Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04709b1bfe | ||
|
|
de7e10c7db | ||
|
|
b652a90184 | ||
|
|
e97e4562b1 | ||
|
|
077d334118 | ||
|
|
291be6a345 | ||
|
|
78d8530df9 | ||
|
|
dc30bfb527 | ||
|
|
aeea2cba0a | ||
|
|
ce96a012ef | ||
|
|
034e6353e8 | ||
|
|
2947353127 | ||
|
|
14a3c3a226 | ||
|
|
42aa1345c3 | ||
|
|
01265e5db0 | ||
|
|
f4afd95cbc | ||
|
|
6c08562aa1 | ||
|
|
4f42ee3f03 | ||
|
|
f3aa1bed4f | ||
|
|
d60fc2bdd0 | ||
|
|
55c8985c12 | ||
|
|
2f9a352352 | ||
|
|
c58c5c58ec | ||
|
|
90b8fdffd3 | ||
|
|
c3e47f896b |
@@ -19,7 +19,7 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
python-version: "3.x"
|
||||
allow-prereleases: true
|
||||
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
|
||||
- name: Install tooling
|
||||
run: |
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
dist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
+13
-13
@@ -39,13 +39,13 @@ jobs:
|
||||
name: Linters (mypy, ruff, etc.)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||
- name: PyLint checks
|
||||
run: uvx nox -s pylint -- --output-format=github
|
||||
@@ -55,14 +55,14 @@ jobs:
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
name: Install Python
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
- name: Install dependencies
|
||||
run: uv sync --no-dev --group test
|
||||
- name: Generate a sample project
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
test_select: android
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
allow-prereleases: true
|
||||
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
|
||||
- name: Free up disk space
|
||||
if: runner.os == 'Linux' && matrix.test_select != 'android'
|
||||
@@ -247,13 +247,13 @@ jobs:
|
||||
outputs:
|
||||
archs: ${{ steps.archs.outputs.archs }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
- name: Install dependencies
|
||||
run: uv sync --no-dev --group test
|
||||
- name: Get qemu emulated architectures
|
||||
@@ -272,13 +272,13 @@ jobs:
|
||||
matrix:
|
||||
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
- name: Install dependencies
|
||||
run: uv sync --no-dev --group test
|
||||
|
||||
@@ -296,14 +296,14 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
name: Install Python 3.12
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --no-dev --group test
|
||||
@@ -348,7 +348,7 @@ jobs:
|
||||
- os: macos-15
|
||||
test_select: android
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download a sample project
|
||||
|
||||
@@ -37,11 +37,11 @@ jobs:
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ repos:
|
||||
- dependency-groups>=1.2
|
||||
- humanize
|
||||
- nox>=2025.2.9
|
||||
- orjson
|
||||
- packaging
|
||||
- pygithub
|
||||
- pytest
|
||||
@@ -77,6 +76,14 @@ repos:
|
||||
files: '^(README\.md|docs/changelog\.md|docs/options\.md|bin/readme.*)$'
|
||||
additional_dependencies: [cogapp>=3.5]
|
||||
|
||||
- repo: https://github.com/henryiii/flake8-lazy
|
||||
rev: 65b94e97b1091634368592d98072160842265fe4 # frozen: v0.8.2
|
||||
hooks:
|
||||
- id: flake8-lazy
|
||||
args: ["--apply=set"]
|
||||
files: '^cibuildwheel'
|
||||
exclude: "^cibuildwheel/resources/"
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
|
||||
hooks:
|
||||
@@ -93,7 +100,6 @@ repos:
|
||||
- id: check-github-workflows
|
||||
- id: check-gitlab-ci
|
||||
- id: check-readthedocs
|
||||
- id: check-travis
|
||||
- id: check-jsonschema
|
||||
name: Check projects
|
||||
args: [--schemafile, docs/data/projects.schema.json]
|
||||
|
||||
@@ -24,13 +24,13 @@ While cibuildwheel itself requires a recent Python version to run (we support th
|
||||
|
||||
| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux<br/>musllinux x86_64 | manylinux<br/>musllinux i686 | manylinux<br/>musllinux aarch64 | manylinux<br/>musllinux ppc64le | manylinux<br/>musllinux s390x | manylinux<br/>musllinux armv7l | Android | iOS | Pyodide |
|
||||
| ------------------------ | ----------- | ------------------- | ------------- | ------------- | -------------- | ------------------------------ | ---------------------------- | ------------------------------- | ------------------------------- | ----------------------------- | ------------------------------ | ------- | --- | -------------- |
|
||||
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | ✅<sup>3</sup> |
|
||||
| CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅ |
|
||||
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅<sup>4</sup> |
|
||||
| CPython 3.15<sup>6</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | N/A |
|
||||
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
|
||||
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | ✅<sup>3</sup> |
|
||||
| CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | ✅ |
|
||||
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | ✅ |
|
||||
| CPython 3.15<sup>5</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | N/A |
|
||||
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
|
||||
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
|
||||
| PyPy 3.11 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
|
||||
@@ -39,9 +39,8 @@ While cibuildwheel itself requires a recent Python version to run (we support th
|
||||
<sup>**1** PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
|
||||
<sup>**2** Windows arm64 support is experimental.</sup><br>
|
||||
<sup>**3** Not supported on PyPI, uses old `pyodide` tag instead of `pyemscripten`. Requires `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
|
||||
<sup>**4** Experimental alpha ABI, do not upload to PyPI yet.</sup><br>
|
||||
<sup>**5** manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.</sup><br>
|
||||
<sup>**6** Python 3.15 requires opt-in using [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
|
||||
<sup>**4** manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.</sup><br>
|
||||
<sup>**5** Python 3.15 requires opt-in using [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
|
||||
|
||||
- Builds manylinux, musllinux, macOS, Windows, pyemscripten, iOS, and Android wheels
|
||||
- Supports CPython, PyPy, and GraalPy
|
||||
@@ -99,7 +98,7 @@ jobs:
|
||||
- uses: actions/setup-python@v6
|
||||
|
||||
- name: Install cibuildwheel
|
||||
run: python -m pip install cibuildwheel==4.0.0
|
||||
run: python -m pip install cibuildwheel==4.1.0
|
||||
|
||||
- name: Build wheels
|
||||
run: python -m cibuildwheel --output-dir wheelhouse
|
||||
@@ -240,6 +239,16 @@ Changelog
|
||||
|
||||
<!-- [[[cog from readme_changelog import mini_changelog; print(mini_changelog()) ]]] -->
|
||||
|
||||
### v4.1.0
|
||||
|
||||
_12 June 2026_
|
||||
|
||||
- ✨ Updates Pyodide to the final 314.0.0 release, so Pyodide 3.14 wheels now build by default without the `pyodide-prerelease` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag. (#2906)
|
||||
- 🐛 Raises clear errors when a build produces no wheel, instead of failing later with a confusing message (#2909)
|
||||
- 🛠 Speeds up CLI startup through lazy imports on Python 3.15 (#2797)
|
||||
- 📚 Adds an FAQ section on caching cibuildwheel's downloaded tools with `CIBW_CACHE_PATH` (#2842)
|
||||
- 📚 Documentation improvements: clarifies which shell is used for command options, clarifies environment variable precedence, and fixes a dead Pyodide env info link (#2904, #2905, #2911)
|
||||
|
||||
### v4.0.0
|
||||
|
||||
_7 June 2026_
|
||||
@@ -249,9 +258,10 @@ See @henryiii's [release post](https://iscinumpy.dev/post/cibuildwheel-4-0-0/) f
|
||||
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
|
||||
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a2, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
|
||||
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
|
||||
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
|
||||
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b2. (#2833, #2850)
|
||||
|
||||
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.15 will be available in cibuildwheel without the flag._
|
||||
|
||||
- ✨ Adds CPython 3.15 support for iOS and Android (#2857, #2858)
|
||||
- ✨ Adds Android improvements for building NumPy and related packages, including auditwheel support, pkg-config and Fortran configuration, and the [`xbuild-files`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-files) option (#2695)
|
||||
- ✨ Adds `CIBUILDWHEEL_BUILD_IDENTIFIER` environment variable set to the current build identifier (e.g. `cp311-manylinux_x86_64`) during per-build steps (#2872)
|
||||
@@ -324,21 +334,7 @@ _5 January 2026_
|
||||
|
||||
- 🛠 Update dependencies and container pins, including updating to CPython 3.14.2. (#2708)
|
||||
|
||||
### v3.3.0
|
||||
|
||||
_12 November 2025_
|
||||
|
||||
- 🐛 Fix an incompatibility with Docker v29 (#2660)
|
||||
- ✨ Adds `test-runtime` option, to customise how tests on simulated/emulated environments are run (#2636)
|
||||
- ✨ Adds support for new `manylinux_2_35` images on 32-bit ARM `armv7l`, offering better C++20 compatibility (#2656)
|
||||
- ✨ `build[uv]` is now supported on Android (#2587)
|
||||
- ✨ You can now install extras (such as `uv`) with a simple option on the GitHub Action (#2630)
|
||||
- ✨ `{project}` and `{package}` placeholders are now supported in `repair-wheel-command` (#2589)
|
||||
- 🛠 The versions set with `dependency-versions` no longer constrain packages specified by your `build-system.requires`. Previously, on platforms other than Linux, the constraints in this option would remain in the environment during the build. This has been tidied up make behaviour more consistent between platforms, and to prevent version conflicts. (#2583)
|
||||
- 🛠 Improve the handling of `test-command` on Android, enabling more options to be passed (#2590)
|
||||
- 📚 Docs improvements (#2618)
|
||||
|
||||
<!-- [[[end]]] (sum: B1EtXSS7u7) -->
|
||||
<!-- [[[end]]] (sum: s3fkxPyqwC) -->
|
||||
|
||||
---
|
||||
|
||||
|
||||
+22
-22
@@ -40,6 +40,28 @@ additionalProperties: false
|
||||
description: cibuildwheel's settings.
|
||||
type: object
|
||||
properties:
|
||||
inherit:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
audit-command: {"$ref": "#/$defs/inherit"}
|
||||
audit-requires: {"$ref": "#/$defs/inherit"}
|
||||
before-all: {"$ref": "#/$defs/inherit"}
|
||||
before-build: {"$ref": "#/$defs/inherit"}
|
||||
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
||||
xbuild-files: {"$ref": "#/$defs/inherit"}
|
||||
before-test: {"$ref": "#/$defs/inherit"}
|
||||
config-settings: {"$ref": "#/$defs/inherit"}
|
||||
container-engine: {"$ref": "#/$defs/inherit"}
|
||||
environment: {"$ref": "#/$defs/inherit"}
|
||||
environment-pass: {"$ref": "#/$defs/inherit"}
|
||||
repair-wheel-command: {"$ref": "#/$defs/inherit"}
|
||||
test-command: {"$ref": "#/$defs/inherit"}
|
||||
test-extras: {"$ref": "#/$defs/inherit"}
|
||||
test-sources: {"$ref": "#/$defs/inherit"}
|
||||
test-requires: {"$ref": "#/$defs/inherit"}
|
||||
test-environment: {"$ref": "#/$defs/inherit"}
|
||||
test-runtime: {"$ref": "#/$defs/inherit"}
|
||||
audit-command:
|
||||
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
|
||||
type: string_array
|
||||
@@ -315,28 +337,6 @@ items:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
select: {}
|
||||
inherit:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
audit-command: {"$ref": "#/$defs/inherit"}
|
||||
audit-requires: {"$ref": "#/$defs/inherit"}
|
||||
before-all: {"$ref": "#/$defs/inherit"}
|
||||
before-build: {"$ref": "#/$defs/inherit"}
|
||||
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
||||
xbuild-files: {"$ref": "#/$defs/inherit"}
|
||||
before-test: {"$ref": "#/$defs/inherit"}
|
||||
config-settings: {"$ref": "#/$defs/inherit"}
|
||||
container-engine: {"$ref": "#/$defs/inherit"}
|
||||
environment: {"$ref": "#/$defs/inherit"}
|
||||
environment-pass: {"$ref": "#/$defs/inherit"}
|
||||
repair-wheel-command: {"$ref": "#/$defs/inherit"}
|
||||
test-command: {"$ref": "#/$defs/inherit"}
|
||||
test-extras: {"$ref": "#/$defs/inherit"}
|
||||
test-sources: {"$ref": "#/$defs/inherit"}
|
||||
test-requires: {"$ref": "#/$defs/inherit"}
|
||||
test-environment: {"$ref": "#/$defs/inherit"}
|
||||
test-runtime: {"$ref": "#/$defs/inherit"}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
+15
-8
@@ -124,14 +124,22 @@ class GraalPyVersions:
|
||||
response.raise_for_status()
|
||||
|
||||
releases = response.json()
|
||||
gp_version_re = re.compile(r"-(\d+\.\d+\.\d+)$")
|
||||
gp_asset_re = re.compile(
|
||||
r"^(?P<prefix>graalpy(?P<cpython>\d+\.\d+)?-(?P<graalpy>\d+\.\d+\.\d+))-"
|
||||
)
|
||||
cp_version_re = re.compile(r"Python (\d+\.\d+(?:\.\d+)?)")
|
||||
for release in releases:
|
||||
m = gp_version_re.search(release["tag_name"])
|
||||
if m:
|
||||
release["graalpy_version"] = Version(m.group(1))
|
||||
m = cp_version_re.search(release["body"])
|
||||
if m:
|
||||
for asset in release["assets"]:
|
||||
m = gp_asset_re.match(asset["name"])
|
||||
if m:
|
||||
release["asset_prefix"] = m.group("prefix")
|
||||
release["graalpy_version"] = Version(m.group("graalpy"))
|
||||
if m.group("cpython"):
|
||||
release["python_version"] = Version(m.group("cpython"))
|
||||
break
|
||||
if "python_version" not in release and (
|
||||
m := cp_version_re.search(release["body"] or "")
|
||||
):
|
||||
release["python_version"] = Version(m.group(1))
|
||||
|
||||
self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r]
|
||||
@@ -172,12 +180,11 @@ class GraalPyVersions:
|
||||
ext = "zip" if "win" in identifier else "tar.gz"
|
||||
for release in reversed(releases):
|
||||
version = release["python_version"]
|
||||
gpversion = release["graalpy_version"]
|
||||
urls = [
|
||||
rf["browser_download_url"]
|
||||
for rf in release["assets"]
|
||||
if rf["name"].endswith(f"{platform}-{arch}.{ext}")
|
||||
and rf["name"].startswith(f"graalpy-{gpversion.major}")
|
||||
and rf["name"].startswith(release["asset_prefix"])
|
||||
]
|
||||
if not urls:
|
||||
continue
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "4.0.0"
|
||||
__version__ = "4.1.0"
|
||||
|
||||
+32
-22
@@ -1,9 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"argparse",
|
||||
"cibuildwheel._compat",
|
||||
"cibuildwheel._compat.tarfile",
|
||||
"cibuildwheel.architecture",
|
||||
"cibuildwheel.ci",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.options",
|
||||
"cibuildwheel.platforms",
|
||||
"cibuildwheel.selector",
|
||||
"cibuildwheel.typing",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.resources",
|
||||
"contextlib",
|
||||
"functools",
|
||||
"io",
|
||||
"pathlib",
|
||||
"shutil",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
"traceback",
|
||||
}
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import functools
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -30,7 +56,7 @@ from cibuildwheel.util.resources import read_all_configs
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator, Iterable, Sequence
|
||||
from typing import Any, Literal, TextIO
|
||||
from typing import Literal
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -44,23 +70,6 @@ class FileReport:
|
||||
size: str
|
||||
|
||||
|
||||
# Taken from https://stackoverflow.com/a/107717
|
||||
class Unbuffered:
|
||||
def __init__(self, stream: TextIO) -> None:
|
||||
self.stream = stream
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
|
||||
def writelines(self, data: Iterable[str]) -> None:
|
||||
self.stream.writelines(data)
|
||||
self.stream.flush()
|
||||
|
||||
def __getattr__(self, attr: str) -> Any: # noqa: ANN401
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
global_options = GlobalOptions()
|
||||
try:
|
||||
@@ -360,10 +369,11 @@ def build_in_directory(args: CommandLineArguments) -> None:
|
||||
# Add CIBUILDWHEEL environment variable
|
||||
os.environ["CIBUILDWHEEL"] = "1"
|
||||
|
||||
# Python is buffering by default when running on the CI platforms, giving
|
||||
# problems interleaving subprocess call output with unflushed calls to
|
||||
# 'print'
|
||||
sys.stdout = Unbuffered(sys.stdout)
|
||||
# Python block-buffers stdout when it isn't a tty, which de-interleaves
|
||||
# `print` output and direct fd-1 writes from subprocesses sharing this
|
||||
# stdout. line_buffering allows each newline to flush all the way to fd-1.
|
||||
if isinstance(sys.stdout, io.TextIOWrapper):
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
|
||||
# create the cache dir before it gets printed & builds performed
|
||||
CIBW_CACHE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"platform", "re", "shutil", "subprocess"}
|
||||
|
||||
import platform as platform_module
|
||||
import re
|
||||
import shutil
|
||||
@@ -105,13 +107,29 @@ class Architecture(StrEnum):
|
||||
case "auto32":
|
||||
result |= cls.bitness_archs(platform=platform, bitness="32")
|
||||
case _:
|
||||
try:
|
||||
result.add(cls(arch_str))
|
||||
except ValueError as e:
|
||||
if arch := cls._parse_arch_name(arch_str, platform=platform):
|
||||
result.add(arch)
|
||||
else:
|
||||
msg = f"Invalid architecture '{arch_str}'"
|
||||
raise errors.ConfigurationError(msg) from e
|
||||
raise errors.ConfigurationError(msg)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _parse_arch_name(cls, arch_str: str, platform: PlatformName) -> Self | None:
|
||||
"""Resolve an architecture name case-insensitively.
|
||||
|
||||
The same value (e.g. "arm64"/"ARM64") can map to different members
|
||||
depending on the platform, so prefer a member valid for ``platform``
|
||||
before falling back to any case-insensitive match.
|
||||
"""
|
||||
for arch in cls.all_archs(platform):
|
||||
if arch.value.lower() == arch_str.lower():
|
||||
return arch
|
||||
for arch in cls:
|
||||
if arch.value.lower() == arch_str.lower():
|
||||
return arch
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def native_arch(cls, platform: PlatformName) -> Self | None:
|
||||
native_machine = platform_module.machine()
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.venv",
|
||||
"pathlib",
|
||||
"subprocess",
|
||||
}
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"bashlex", "subprocess"}
|
||||
|
||||
import dataclasses
|
||||
import subprocess
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
__lazy_modules__ = {"cibuildwheel.util", "cibuildwheel.util.helpers"}
|
||||
|
||||
import os
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"bashlex", "bashlex.errors"}
|
||||
|
||||
import dataclasses
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ a different return code, by defining them all here, we can ensure that they're
|
||||
semantically clear and unique.
|
||||
"""
|
||||
|
||||
__lazy_modules__ = {"textwrap"}
|
||||
|
||||
import textwrap
|
||||
|
||||
|
||||
@@ -109,3 +111,19 @@ class AuditCommandFailedError(FatalError):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.return_code = 9
|
||||
|
||||
|
||||
class BuildProducedNoWheelError(FatalError):
|
||||
def __init__(self) -> None:
|
||||
message = textwrap.dedent(
|
||||
"""
|
||||
Build failed because the build frontend completed successfully but
|
||||
did not produce a wheel.
|
||||
|
||||
This usually indicates a problem with your project configuration or
|
||||
build backend. Check your project configuration, or run cibuildwheel
|
||||
with CIBW_BUILD_VERBOSITY=1 to view build logs.
|
||||
"""
|
||||
)
|
||||
super().__init__(message)
|
||||
self.return_code = 10
|
||||
|
||||
@@ -4,6 +4,8 @@ These are utilities for the `/bin` scripts, not for the `cibuildwheel` program.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"io", "json", "urllib", "urllib.error", "urllib.request"}
|
||||
|
||||
import json
|
||||
import time
|
||||
import typing
|
||||
@@ -103,7 +105,7 @@ class PyodideXBuildEnvInfo(typing.TypedDict):
|
||||
|
||||
def get_pyodide_xbuildenv_info() -> PyodideXBuildEnvInfo:
|
||||
xbuildenv_info_url = (
|
||||
"https://pyodide.github.io/pyodide/api/pyodide-cross-build-environments.json"
|
||||
"https://pyodide.github.io/pyodide/api/v2/pyodide-cross-build-environments.json"
|
||||
)
|
||||
with urllib.request.urlopen(xbuildenv_info_url) as response:
|
||||
return typing.cast("PyodideXBuildEnvInfo", json.loads(response.read().decode("utf-8")))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"cibuildwheel.util", "cibuildwheel.util.helpers", "shlex"}
|
||||
|
||||
import dataclasses
|
||||
import shlex
|
||||
import typing
|
||||
|
||||
+14
-2
@@ -1,5 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.ci",
|
||||
"contextlib",
|
||||
"functools",
|
||||
"hashlib",
|
||||
"humanize",
|
||||
"io",
|
||||
"pathlib",
|
||||
"re",
|
||||
"textwrap",
|
||||
}
|
||||
|
||||
import codecs
|
||||
import contextlib
|
||||
import dataclasses
|
||||
@@ -363,11 +375,11 @@ class Logger:
|
||||
out.write("\n")
|
||||
return out.getvalue()
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def colors(self) -> Colors:
|
||||
return Colors(enabled=self.colors_enabled)
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def symbols(self) -> Symbols:
|
||||
return Symbols(unicode=self.unicode_enabled)
|
||||
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.ci",
|
||||
"cibuildwheel.errors",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.helpers",
|
||||
"contextlib",
|
||||
"io",
|
||||
"json",
|
||||
"platform",
|
||||
"shlex",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"textwrap",
|
||||
"uuid",
|
||||
}
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import io
|
||||
|
||||
+153
-49
@@ -1,5 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.architecture",
|
||||
"cibuildwheel.environment",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.oci_container",
|
||||
"cibuildwheel.projectfiles",
|
||||
"cibuildwheel.selector",
|
||||
"cibuildwheel.typing",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"collections",
|
||||
"configparser",
|
||||
"contextlib",
|
||||
"difflib",
|
||||
"packaging",
|
||||
"packaging.specifiers",
|
||||
"pathlib",
|
||||
"shlex",
|
||||
"textwrap",
|
||||
"tomllib",
|
||||
}
|
||||
|
||||
import collections
|
||||
import configparser
|
||||
import contextlib
|
||||
@@ -26,7 +50,13 @@ from cibuildwheel.projectfiles import get_requires_python_str, resolve_dependenc
|
||||
from cibuildwheel.selector import BuildSelector, EnableGroup, TestSelector, selector_matches
|
||||
from cibuildwheel.typing import PLATFORMS, PlatformName
|
||||
from cibuildwheel.util import resources
|
||||
from cibuildwheel.util.helpers import format_safe, parse_key_value_string, strtobool, unwrap
|
||||
from cibuildwheel.util.helpers import (
|
||||
format_safe,
|
||||
parse_arbitrary_key_value_string,
|
||||
parse_key_value_string,
|
||||
strtobool,
|
||||
unwrap,
|
||||
)
|
||||
from cibuildwheel.util.packaging import DependencyConstraints
|
||||
|
||||
TYPE_CHECKING = False
|
||||
@@ -412,6 +442,28 @@ def _stringify_setting(
|
||||
return setting
|
||||
|
||||
|
||||
def parse_inherit(config: str | dict[str, str] | None) -> dict[str, InheritRule]:
|
||||
inherit_dict: dict[str, str]
|
||||
|
||||
if config is None:
|
||||
return {}
|
||||
|
||||
if isinstance(config, str):
|
||||
parsed = parse_arbitrary_key_value_string(config, default_value="append")
|
||||
inherit_dict = {k: "".join(v) for k, v in parsed.items()}
|
||||
elif isinstance(config, dict):
|
||||
inherit_dict = config
|
||||
else:
|
||||
msg = "'inherit' must be a string or a table"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
if not all(v in {"none", "append", "prepend"} for v in inherit_dict.values()):
|
||||
msg = "'inherit' must contain only {'none', 'append', 'prepend'} values"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
return {k: InheritRule[v.upper()] for k, v in inherit_dict.items()}
|
||||
|
||||
|
||||
class OptionsReader:
|
||||
"""
|
||||
Gets options from the environment, config or defaults, optionally scoped
|
||||
@@ -460,45 +512,18 @@ class OptionsReader:
|
||||
self._validate_platform_option(option_name)
|
||||
|
||||
self.config_options = config_options
|
||||
self.config_options_inherit = parse_inherit(config_options.get("inherit"))
|
||||
self.config_platform_options = config_platform_options
|
||||
self.config_platform_options_inherit = parse_inherit(config_platform_options.get("inherit"))
|
||||
|
||||
self.overrides: list[Override] = []
|
||||
self.current_identifier: str | None = None
|
||||
|
||||
config_overrides = self.config_options.get("overrides")
|
||||
|
||||
if config_overrides is not None:
|
||||
if not isinstance(config_overrides, list):
|
||||
msg = "'tool.cibuildwheel.overrides' must be a list"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
for config_override in config_overrides:
|
||||
select = config_override.pop("select", None)
|
||||
|
||||
if not select:
|
||||
msg = "'select' must be set in an override"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
if isinstance(select, list):
|
||||
select = " ".join(select)
|
||||
|
||||
inherit = config_override.pop("inherit", {})
|
||||
if not isinstance(inherit, dict) or not all(
|
||||
i in {"none", "append", "prepend"} for i in inherit.values()
|
||||
):
|
||||
msg = "'inherit' must be a dict containing only {'none', 'append', 'prepend'} values"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
inherit_enum = {k: InheritRule[v.upper()] for k, v in inherit.items()}
|
||||
|
||||
self.overrides.append(Override(select, config_override, inherit_enum))
|
||||
|
||||
def _validate_global_option(self, name: str) -> None:
|
||||
"""
|
||||
Raises an error if an option with this name is not allowed in the
|
||||
[tool.cibuildwheel] section of a config file.
|
||||
"""
|
||||
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
|
||||
allowed_option_names = self.default_options.keys() | PLATFORMS | {"inherit", "overrides"}
|
||||
|
||||
if name not in allowed_option_names:
|
||||
msg = f"Option {name!r} not supported in a config file."
|
||||
@@ -517,7 +542,9 @@ class OptionsReader:
|
||||
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
|
||||
allowed_option_names = (
|
||||
self.default_options.keys() | self.default_platform_options.keys() | {"inherit"}
|
||||
)
|
||||
|
||||
if name not in allowed_option_names:
|
||||
msg = f"Option {name!r} not supported in the {self.platform!r} section"
|
||||
@@ -538,6 +565,56 @@ class OptionsReader:
|
||||
|
||||
return global_options, platform_options
|
||||
|
||||
@functools.cached_property
|
||||
def overrides(self) -> list[Override]:
|
||||
config_overrides = self.config_options.get("overrides")
|
||||
overrides: list[Override] = []
|
||||
|
||||
if config_overrides is not None:
|
||||
if not isinstance(config_overrides, list):
|
||||
msg = "'tool.cibuildwheel.overrides' must be a list"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
for config_override in config_overrides:
|
||||
select = config_override.pop("select", None)
|
||||
|
||||
if not select:
|
||||
msg = "'select' must be set in an override"
|
||||
raise OptionsReaderError(msg)
|
||||
|
||||
if isinstance(select, list):
|
||||
select = " ".join(select)
|
||||
|
||||
inherit = config_override.pop("inherit", {})
|
||||
|
||||
overrides.append(Override(select, config_override, parse_inherit(inherit)))
|
||||
|
||||
return overrides
|
||||
|
||||
@functools.cached_property
|
||||
def env_inherit(self) -> dict[str, InheritRule]:
|
||||
env_inherit_str = self.env.get("CIBW_INHERIT", "")
|
||||
try:
|
||||
return parse_inherit(env_inherit_str)
|
||||
except OptionsReaderError as e:
|
||||
msg = f"Failed to parse CIBW_INHERIT environment variable. {e}"
|
||||
raise errors.ConfigurationError(msg) from e
|
||||
|
||||
@functools.cached_property
|
||||
def env_platform_inherit(self) -> dict[str, InheritRule]:
|
||||
env_inherit = self.env_inherit
|
||||
|
||||
# find the rules which have -{platform} on the end of their key,
|
||||
# remove the platform suffix from the key and return the resulting
|
||||
# rule.
|
||||
platform_suffix = f"-{self.platform}"
|
||||
|
||||
return {
|
||||
key.removesuffix(platform_suffix): value
|
||||
for key, value in env_inherit.items()
|
||||
if key.endswith(platform_suffix)
|
||||
}
|
||||
|
||||
@property
|
||||
def active_config_overrides(self) -> list[Override]:
|
||||
if self.current_identifier is None:
|
||||
@@ -561,7 +638,7 @@ class OptionsReader:
|
||||
env_plat: bool = True,
|
||||
option_format: OptionFormat | None = None,
|
||||
ignore_empty: bool = False,
|
||||
env_rule: InheritRule = InheritRule.NONE,
|
||||
default_env_rule: InheritRule = InheritRule.NONE,
|
||||
) -> str:
|
||||
"""
|
||||
Get and return the value for the named option from environment,
|
||||
@@ -585,16 +662,37 @@ class OptionsReader:
|
||||
# get the option from the default, then the config file, then finally the environment.
|
||||
# platform-specific options are preferred, if they're allowed.
|
||||
return _resolve_cascade(
|
||||
(self.default_options.get(name), InheritRule.NONE),
|
||||
(self.default_platform_options.get(name), InheritRule.NONE),
|
||||
(self.config_options.get(name), InheritRule.NONE),
|
||||
(self.config_platform_options.get(name), InheritRule.NONE),
|
||||
(
|
||||
self.default_options.get(name),
|
||||
InheritRule.NONE,
|
||||
),
|
||||
(
|
||||
self.default_platform_options.get(name),
|
||||
InheritRule.NONE,
|
||||
),
|
||||
(
|
||||
self.config_options.get(name),
|
||||
self.config_options_inherit.get(name, InheritRule.NONE),
|
||||
),
|
||||
(
|
||||
self.config_platform_options.get(name),
|
||||
self.config_platform_options_inherit.get(name, InheritRule.NONE),
|
||||
),
|
||||
*[
|
||||
(o.options.get(name), o.inherit.get(name, InheritRule.NONE))
|
||||
(
|
||||
o.options.get(name),
|
||||
o.inherit.get(name, InheritRule.NONE),
|
||||
)
|
||||
for o in self.active_config_overrides
|
||||
],
|
||||
(self.env.get(envvar), env_rule),
|
||||
(self.env.get(plat_envvar) if env_plat else None, env_rule),
|
||||
(
|
||||
self.env.get(envvar),
|
||||
self.env_inherit.get(name, default_env_rule),
|
||||
),
|
||||
(
|
||||
self.env.get(plat_envvar) if env_plat else None,
|
||||
self.env_platform_inherit.get(name, default_env_rule),
|
||||
),
|
||||
ignore_empty=ignore_empty,
|
||||
option_format=option_format,
|
||||
)
|
||||
@@ -667,7 +765,10 @@ class Options:
|
||||
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",
|
||||
env_plat=False,
|
||||
option_format=ListFormat(sep=" "),
|
||||
default_env_rule=InheritRule.APPEND,
|
||||
)
|
||||
try:
|
||||
enable = {
|
||||
@@ -773,12 +874,11 @@ class Options:
|
||||
if xbuild_tools == ["\u0000"]:
|
||||
xbuild_tools = None
|
||||
|
||||
xbuild_files = parse_key_value_string(
|
||||
xbuild_files = parse_arbitrary_key_value_string(
|
||||
self.reader.get(
|
||||
"xbuild-files",
|
||||
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
|
||||
),
|
||||
kw_arg_names=["*"],
|
||||
)
|
||||
|
||||
test_sources = shlex.split(
|
||||
@@ -809,9 +909,11 @@ class Options:
|
||||
msg = f"Failed to parse test runtime config. {e}"
|
||||
raise errors.ConfigurationError(msg) from e
|
||||
|
||||
test_requires = self.reader.get(
|
||||
"test-requires", option_format=ListFormat(sep=" ")
|
||||
).split()
|
||||
test_requires = shlex.split(
|
||||
self.reader.get(
|
||||
"test-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||
)
|
||||
)
|
||||
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]
|
||||
@@ -908,9 +1010,11 @@ class Options:
|
||||
)
|
||||
audit_command = audit_command_str.split(" && ") if audit_command_str else []
|
||||
|
||||
audit_requires = self.reader.get(
|
||||
"audit-requires", option_format=ListFormat(sep=" ")
|
||||
).split()
|
||||
audit_requires = shlex.split(
|
||||
self.reader.get(
|
||||
"audit-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||
)
|
||||
)
|
||||
|
||||
return BuildOptions(
|
||||
globals=self.globals,
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"build",
|
||||
"build.env",
|
||||
"cibuildwheel.architecture",
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.util.python_build_standalone",
|
||||
"cibuildwheel.venv",
|
||||
"filelock",
|
||||
"packaging",
|
||||
"packaging.utils",
|
||||
"pathlib",
|
||||
"platform",
|
||||
"pprint",
|
||||
"re",
|
||||
"runpy",
|
||||
"shlex",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"textwrap",
|
||||
"typing",
|
||||
}
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
@@ -410,10 +438,14 @@ def setup_android_env(
|
||||
# * a key=value string, without quotes
|
||||
for i, token in enumerate(shlex.split(env_output)):
|
||||
if i % 2 == 0:
|
||||
assert token == "export", token
|
||||
if token != "export":
|
||||
msg = f"Unexpected output from android.py env: expected 'export', got {token!r}"
|
||||
raise errors.FatalError(msg)
|
||||
else:
|
||||
key, sep, value = token.partition("=")
|
||||
assert sep == "=", token
|
||||
if sep != "=":
|
||||
msg = f"Unexpected output from android.py env: expected 'key=value', got {token!r}"
|
||||
raise errors.FatalError(msg)
|
||||
android_env[key] = value
|
||||
|
||||
# localized_vars cleared the CFLAGS and CXXFLAGS in the sysconfigdata, but most
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.platforms.macos",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.venv",
|
||||
"filelock",
|
||||
"packaging",
|
||||
"packaging.version",
|
||||
"pathlib",
|
||||
"platform",
|
||||
"shlex",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"textwrap",
|
||||
"typing",
|
||||
}
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
import platform
|
||||
@@ -95,6 +118,14 @@ def all_python_configurations() -> list[PythonConfiguration]:
|
||||
matching = [
|
||||
config for config in macos_python_configs if config["identifier"] == macos_identifier
|
||||
]
|
||||
if not matching:
|
||||
msg = (
|
||||
f"Internal error: no macOS configuration found matching "
|
||||
f"{macos_identifier!r}, needed to build the iOS configuration "
|
||||
f"{config_dict['identifier']!r}. The bundled build-platforms.toml "
|
||||
f"resources are inconsistent."
|
||||
)
|
||||
raise errors.FatalError(msg)
|
||||
return matching[0]
|
||||
|
||||
# Load the platform configuration
|
||||
@@ -560,7 +591,10 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
case _:
|
||||
assert_never(build_frontend)
|
||||
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
try:
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
except StopIteration:
|
||||
raise errors.BuildProducedNoWheelError() from None
|
||||
|
||||
if built_wheel.name.endswith("none-any.whl"):
|
||||
raise errors.NonPlatformWheelError()
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"collections",
|
||||
"contextlib",
|
||||
"pathlib",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"textwrap",
|
||||
"typing",
|
||||
}
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import shutil
|
||||
@@ -346,7 +363,10 @@ def build_in_container(
|
||||
case _:
|
||||
assert_never(build_frontend)
|
||||
|
||||
built_wheel = container.glob(built_wheel_dir, "*.whl")[0]
|
||||
try:
|
||||
built_wheel = container.glob(built_wheel_dir, "*.whl")[0]
|
||||
except IndexError:
|
||||
raise errors.BuildProducedNoWheelError() from None
|
||||
|
||||
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
||||
container.call(["rm", "-rf", repaired_wheel_dir])
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.ci",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.venv",
|
||||
"filelock",
|
||||
"inspect",
|
||||
"packaging",
|
||||
"packaging.version",
|
||||
"pathlib",
|
||||
"platform",
|
||||
"re",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
}
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import inspect
|
||||
@@ -566,7 +588,10 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
case _:
|
||||
assert_never(build_frontend)
|
||||
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
try:
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
except StopIteration:
|
||||
raise errors.BuildProducedNoWheelError() from None
|
||||
|
||||
repaired_wheel_dir.mkdir()
|
||||
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.architecture",
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.util.python_build_standalone",
|
||||
"cibuildwheel.venv",
|
||||
"filelock",
|
||||
"json",
|
||||
"pathlib",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"tempfile",
|
||||
"tomllib",
|
||||
}
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import json
|
||||
@@ -197,6 +218,36 @@ def validate_pyodide_build_version(
|
||||
raise errors.FatalError(msg)
|
||||
|
||||
|
||||
def validate_pyodide_target_python(
|
||||
xbuildenv_info: PyodideXBuildEnvInfo, python_configuration: PythonConfiguration
|
||||
) -> None:
|
||||
"""
|
||||
Validate that the resolved Pyodide xbuildenv targets the same Python version
|
||||
as the identifier being built.
|
||||
|
||||
This is to catch the case where a global ``pyodide-version`` is applied to an
|
||||
identifier whose Python version does not match, such as setting
|
||||
``pyodide-version = "3Y.0.0"`` (a Python 3.Y environment) while
|
||||
building ``cp3X-pyodide_wasm32``.
|
||||
"""
|
||||
xbuildenv_version = xbuildenv_info["version"]
|
||||
identifier = python_configuration.identifier
|
||||
expected_version = python_configuration.version
|
||||
default_version = python_configuration.default_pyodide_version
|
||||
xbuildenv_python_minor = ".".join(xbuildenv_info["python"].split(".")[:2])
|
||||
if xbuildenv_python_minor != expected_version:
|
||||
msg = unwrap_preserving_paragraphs(f"""
|
||||
The `pyodide-version` option is set to {xbuildenv_version}, which
|
||||
provides Python {xbuildenv_python_minor}, but the {identifier} build
|
||||
needs Python {expected_version}.
|
||||
|
||||
Either remove `pyodide-version` so {identifier} uses its default
|
||||
({default_version}), or stop building {identifier} so that
|
||||
`pyodide-version` only applies to an identifier it matches.
|
||||
""")
|
||||
raise errors.ConfigurationError(msg)
|
||||
|
||||
|
||||
def install_xbuildenv(env: dict[str, str], xbuildenv_cache_path: Path, pyodide_version: str) -> str:
|
||||
"""Install a particular Pyodide xbuildenv version and set a path to the Pyodide root."""
|
||||
pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root"
|
||||
@@ -316,6 +367,10 @@ def setup_python(
|
||||
xbuildenv_info=xbuildenv_info,
|
||||
pyodide_build_version=pyodide_build_version,
|
||||
)
|
||||
validate_pyodide_target_python(
|
||||
xbuildenv_info=xbuildenv_info,
|
||||
python_configuration=python_configuration,
|
||||
)
|
||||
|
||||
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}"
|
||||
|
||||
@@ -451,7 +506,10 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
*extra_flags,
|
||||
env=env,
|
||||
)
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
try:
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
except StopIteration:
|
||||
raise errors.BuildProducedNoWheelError() from None
|
||||
|
||||
if built_wheel.name.endswith("none-any.whl"):
|
||||
raise errors.NonPlatformWheelError()
|
||||
@@ -471,7 +529,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)
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.architecture",
|
||||
"cibuildwheel.audit",
|
||||
"cibuildwheel.frontend",
|
||||
"cibuildwheel.logger",
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.helpers",
|
||||
"cibuildwheel.util.packaging",
|
||||
"cibuildwheel.venv",
|
||||
"filelock",
|
||||
"pathlib",
|
||||
"platform",
|
||||
"shutil",
|
||||
"subprocess",
|
||||
"textwrap",
|
||||
"typing",
|
||||
}
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
import platform as platform_module
|
||||
@@ -328,7 +348,7 @@ def setup_python(
|
||||
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"):
|
||||
if where_pip != 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)
|
||||
@@ -522,7 +542,10 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
case _:
|
||||
assert_never(build_frontend)
|
||||
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
try:
|
||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||
except StopIteration:
|
||||
raise errors.BuildProducedNoWheelError() from None
|
||||
|
||||
# repair the wheel
|
||||
repaired_wheel_dir.mkdir()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"configparser", "contextlib", "dependency_groups"}
|
||||
|
||||
import ast
|
||||
import configparser
|
||||
import contextlib
|
||||
|
||||
@@ -217,7 +217,7 @@ python_configurations = [
|
||||
python_configurations = [
|
||||
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22", sha256 = "0c2e7db42efa5d1dac38b50f8b3d659a1e3885d0a233494831b8206281307d52" },
|
||||
{ identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.4", node_version = "v22", sha256 = "a29fc4a076408a18fc29eb4b280f80c6dddc95c19514c874c29e590d0931c02a" },
|
||||
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24", sha256 = "ac7bbcdf289ab3ae25621c436b1d013099ef6bec644ad0538dae5d65552c5c95" },
|
||||
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0", node_version = "v24", sha256 = "6e76cf18251bd7d046bdb7ef35c9f938c8e995f10d88397497ca4b7020f27ba6" },
|
||||
]
|
||||
|
||||
[android]
|
||||
|
||||
@@ -26,6 +26,67 @@
|
||||
"description": "cibuildwheel's settings.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-all": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-build": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"xbuild-files": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-test": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"config-settings": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"container-engine": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"environment-pass": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-extras": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-environment": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-runtime": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
}
|
||||
},
|
||||
"title": "CIBW_INHERIT"
|
||||
},
|
||||
"audit-command": {
|
||||
"description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.",
|
||||
"oneOf": [
|
||||
@@ -689,64 +750,7 @@
|
||||
]
|
||||
},
|
||||
"inherit": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-all": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-build": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"xbuild-files": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-test": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"config-settings": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"container-engine": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"environment-pass": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-extras": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-sources": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-environment": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"test-runtime": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
}
|
||||
}
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
@@ -875,6 +879,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
@@ -1014,6 +1021,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
@@ -1099,6 +1109,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
@@ -1184,6 +1197,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
@@ -1256,6 +1272,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
@@ -1341,6 +1360,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"inherit": {
|
||||
"$ref": "#/properties/inherit"
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
|
||||
# via pydantic
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyodide-build==0.34.4
|
||||
pyodide-build==0.35.0
|
||||
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
|
||||
pyodide-cli==0.5.0
|
||||
# via
|
||||
|
||||
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
|
||||
# via pydantic
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyodide-build==0.34.4
|
||||
pyodide-build==0.35.0
|
||||
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
|
||||
pyodide-cli==0.5.0
|
||||
# via
|
||||
|
||||
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
|
||||
# via pydantic
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyodide-build==0.34.4
|
||||
pyodide-build==0.35.0
|
||||
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
|
||||
pyodide-cli==0.5.0
|
||||
# via
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"cibuildwheel.util", "json"}
|
||||
|
||||
import json
|
||||
|
||||
from cibuildwheel.util import resources
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"bracex", "fnmatch", "itertools", "packaging", "packaging.version"}
|
||||
|
||||
import dataclasses
|
||||
import itertools
|
||||
from enum import StrEnum
|
||||
@@ -102,19 +104,19 @@ class BuildSelector:
|
||||
return False
|
||||
if EnableGroup.PyodideEoL not in self.enable and fnmatch(build_id, "cp312-pyodide_*"):
|
||||
return False
|
||||
# NOTE: Disable this when we don't have any Pyodide prereleases (e.g., 314.0.0a1+)
|
||||
# NOTE: Re-enable this when we have a new Pyodide prerelease (e.g., 315.0.0a1+)
|
||||
# When doing this, also:
|
||||
# 1. update Pyodide tests in unit_test/build_selector_test.py and unit_test/options_test.py accordingly.
|
||||
# 2. update Python versions for Pyodide identifiers in cibuildwheel/selector.py.
|
||||
# 3. update constraints as necessary via bin/generate_pyodide_constraints.py and add/delete
|
||||
# Pyodide constraints files in cibuildwheel/resources/constraints/ as necessary.
|
||||
# When disabling, update the pattern to match the experimental Python version in case
|
||||
# it is bumped to Python 3.15 (likely cp315-pyodide_* but could remain as 3.14 as well).
|
||||
# When re-enabling, update the pattern to match the experimental Python version when
|
||||
# it is bumped to Python 3.15 (likely cp315-pyodide_*).
|
||||
# This depends on the CPython version being used in the Pyodide runtime at the time.
|
||||
if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
|
||||
build_id, "cp314-pyodide_*"
|
||||
):
|
||||
return False
|
||||
# if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
|
||||
# build_id, "cp315-pyodide_*"
|
||||
# ):
|
||||
# return False
|
||||
|
||||
should_build = selector_matches(self.build_config, build_id)
|
||||
should_skip = selector_matches(self.skip_config, build_id)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"cibuildwheel.errors", "shlex", "shutil", "subprocess"}
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"certifi",
|
||||
"cibuildwheel.errors",
|
||||
"hashlib",
|
||||
"shutil",
|
||||
"ssl",
|
||||
"tarfile",
|
||||
"typing",
|
||||
"urllib",
|
||||
"urllib.request",
|
||||
"zipfile",
|
||||
}
|
||||
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
@@ -85,7 +99,8 @@ def download(url: str, dest: Path, *, sha256: str | None = None) -> None:
|
||||
time.sleep(3)
|
||||
|
||||
if sha256:
|
||||
computed = hashlib.sha256(dest.read_bytes()).hexdigest()
|
||||
with dest.open("rb") as f:
|
||||
computed = hashlib.file_digest(f, "sha256").hexdigest()
|
||||
if computed != sha256:
|
||||
dest.unlink(missing_ok=True)
|
||||
msg = f"SHA256 mismatch for {url}: expected {sha256!r}, got {computed!r}"
|
||||
@@ -116,6 +131,8 @@ def extract_tar(tar_src: Path, dest: Path) -> None:
|
||||
See: https://docs.python.org/3/library/tarfile.html#tarfile.tar_filter for filter details
|
||||
"""
|
||||
with tarfile.open(tar_src) as tar_:
|
||||
# getattr shim needed while Python 3.11.0-3.11.3 are supported;
|
||||
# once the minimum is 3.11.4+/3.12, replace with: tar_.extractall(dest, filter="tar")
|
||||
tar_.extraction_filter = getattr(tarfile, "tar_filter", (lambda member, _: member))
|
||||
tar_.extractall(dest)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"collections", "itertools", "re", "shlex", "textwrap"}
|
||||
|
||||
import dataclasses
|
||||
import itertools
|
||||
import re
|
||||
@@ -109,7 +111,7 @@ def parse_key_value_string(
|
||||
if kw_arg_names is None:
|
||||
kw_arg_names = []
|
||||
|
||||
all_field_names = None if ("*" in kw_arg_names) else [*positional_arg_names, *kw_arg_names]
|
||||
all_field_names = [*positional_arg_names, *kw_arg_names]
|
||||
|
||||
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
||||
shlexer.commenters = ""
|
||||
@@ -126,7 +128,7 @@ def parse_key_value_string(
|
||||
# check to see if the option name is specified
|
||||
field_name, sep, first_value = field[0].partition(":")
|
||||
if sep:
|
||||
if (all_field_names is not None) and (field_name not in all_field_names):
|
||||
if field_name not in all_field_names:
|
||||
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -145,6 +147,51 @@ def parse_key_value_string(
|
||||
return dict(result)
|
||||
|
||||
|
||||
def parse_arbitrary_key_value_string(
|
||||
key_value_string: str, default_value: str | None = None
|
||||
) -> dict[str, list[str]]:
|
||||
"""
|
||||
Parses a string like
|
||||
|
||||
"before-build; before-test: append; after-test: prepend"
|
||||
or
|
||||
"package1: some/header.h some/library.a; package2: other/header.h"
|
||||
|
||||
There are no restrictions on the keys than can be set.
|
||||
|
||||
No positional arguments are allowed. Words without a colon attached are
|
||||
interpreted as keys. Keys without a value will be assigned the
|
||||
default_value if provided, otherwise throw an error.
|
||||
"""
|
||||
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
||||
shlexer.commenters = ""
|
||||
shlexer.whitespace_split = True
|
||||
parts = list(shlexer)
|
||||
# parts now looks like
|
||||
# ['before-build', ';', 'before-test:', 'append', ';', 'after-test:', 'prepend']
|
||||
|
||||
# split by semicolon
|
||||
result: defaultdict[str, list[str]] = defaultdict(list)
|
||||
fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k]
|
||||
for field in fields:
|
||||
# check to see if the option name is specified
|
||||
field_name, sep, first_value = field[0].partition(":")
|
||||
if sep:
|
||||
# the colon was present, so the first value is the value after the colon
|
||||
values = ([first_value] if first_value else []) + field[1:]
|
||||
result[field_name] += values
|
||||
else:
|
||||
# no colon, so it's a key (or set of keys) without values
|
||||
if default_value is None:
|
||||
msg = f"Failed to parse {key_value_string!r}. No value specified for {field_name!r}. Expected ':' followed by a value."
|
||||
raise ValueError(msg)
|
||||
|
||||
for key in field:
|
||||
result[key].append(default_value)
|
||||
|
||||
return dict(result)
|
||||
|
||||
|
||||
@dataclasses.dataclass(order=True)
|
||||
class FlexibleVersion:
|
||||
version_parts: tuple[int, ...] = dataclasses.field(init=False, repr=False)
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.helpers",
|
||||
"packaging",
|
||||
"packaging.utils",
|
||||
"shlex",
|
||||
}
|
||||
|
||||
import shlex
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.util.file",
|
||||
"cibuildwheel.util.resources",
|
||||
"filelock",
|
||||
"fnmatch",
|
||||
"hashlib",
|
||||
"json",
|
||||
"platform",
|
||||
"subprocess",
|
||||
}
|
||||
|
||||
import fnmatch
|
||||
import functools
|
||||
import hashlib
|
||||
@@ -143,7 +154,8 @@ def _download_or_get_from_cache(
|
||||
asset_cache_path = cache_dir / asset_filename
|
||||
if asset_cache_path.is_file():
|
||||
if sha256:
|
||||
computed = hashlib.sha256(asset_cache_path.read_bytes()).hexdigest()
|
||||
with asset_cache_path.open("rb") as f:
|
||||
computed = hashlib.file_digest(f, "sha256").hexdigest()
|
||||
if computed != sha256:
|
||||
print(
|
||||
f"Cached python_build_standalone SHA256 mismatch for {asset_cache_path}; redownloading."
|
||||
@@ -218,9 +230,7 @@ def create_python_build_standalone_environment(
|
||||
)
|
||||
|
||||
python_base_dir = temp_dir / "pbs"
|
||||
if python_base_dir.exists():
|
||||
msg = f"python-build-standalone directory already exists: {python_base_dir}"
|
||||
raise PythonBuildStandaloneError(msg)
|
||||
assert not python_base_dir.exists()
|
||||
extract_tar(archive_path, python_base_dir)
|
||||
|
||||
return _find_python_executable(python_base_dir)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {"tomllib"}
|
||||
|
||||
import functools
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__lazy_modules__ = {
|
||||
"cibuildwheel.util",
|
||||
"cibuildwheel.util.cmd",
|
||||
"cibuildwheel.util.file",
|
||||
"contextlib",
|
||||
"filelock",
|
||||
"packaging",
|
||||
"packaging.markers",
|
||||
"packaging.requirements",
|
||||
"packaging.version",
|
||||
"pathlib",
|
||||
"shutil",
|
||||
"tomllib",
|
||||
}
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
|
||||
+12
-1
@@ -5,6 +5,16 @@ ref: changelog
|
||||
|
||||
# Changelog
|
||||
|
||||
### v4.1.0
|
||||
|
||||
_12 June 2026_
|
||||
|
||||
- ✨ Updates Pyodide to the final 314.0.0 release, so Pyodide 3.14 wheels now build by default without the `pyodide-prerelease` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag. (#2906)
|
||||
- 🐛 Raises clear errors when a build produces no wheel, instead of failing later with a confusing message (#2909)
|
||||
- 🛠 Speeds up CLI startup through lazy imports on Python 3.15 (#2797)
|
||||
- 📚 Adds an FAQ section on caching cibuildwheel's downloaded tools with `CIBW_CACHE_PATH` (#2842)
|
||||
- 📚 Documentation improvements: clarifies which shell is used for command options, clarifies environment variable precedence, and fixes a dead Pyodide env info link (#2904, #2905, #2911)
|
||||
|
||||
### v4.0.0
|
||||
|
||||
_7 June 2026_
|
||||
@@ -14,9 +24,10 @@ See @henryiii's [release post](https://iscinumpy.dev/post/cibuildwheel-4-0-0/) f
|
||||
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
|
||||
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a2, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
|
||||
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
|
||||
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
|
||||
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b2. (#2833, #2850)
|
||||
|
||||
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.15 will be available in cibuildwheel without the flag._
|
||||
|
||||
- ✨ Adds CPython 3.15 support for iOS and Android (#2857, #2858)
|
||||
- ✨ Adds Android improvements for building NumPy and related packages, including auditwheel support, pkg-config and Fortran configuration, and the [`xbuild-files`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-files) option (#2695)
|
||||
- ✨ Adds `CIBUILDWHEEL_BUILD_IDENTIFIER` environment variable set to the current build identifier (e.g. `cp311-manylinux_x86_64`) during per-build steps (#2872)
|
||||
|
||||
+73
-18
@@ -64,10 +64,13 @@ You can configure cibuildwheel with a config file, such as `pyproject.toml`.
|
||||
Options have the same names as the environment variable overrides, but are
|
||||
placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
|
||||
common [TOML](https://toml.io) practice. Anything placed in subsections
|
||||
named after a platform will only affect those platforms. Lists can be used
|
||||
instead of strings for items that are naturally a list. Multiline strings also
|
||||
work just like in the environment variables. Environment variables will take
|
||||
precedence if defined.
|
||||
named after a platform will only affect those platforms. Platform-specific
|
||||
values replace the corresponding global value for that platform; table options
|
||||
are not merged key by key unless you configure [inheritance](#inherit). Lists can
|
||||
be used instead of strings for items that are naturally a list. Multiline strings
|
||||
also work just like in the environment variables. Environment variable overrides,
|
||||
such as `CIBW_TEST_COMMAND` and `CIBW_TEST_COMMAND_LINUX`, take precedence if
|
||||
defined.
|
||||
|
||||
The example above using environment variables could have been written like this:
|
||||
|
||||
@@ -111,9 +114,9 @@ trigger new containers, one per image.
|
||||
|
||||
The ``output-dir``, ``build``, ``skip``, ``test_skip`` selectors, and architectures cannot be overridden.
|
||||
|
||||
You can specify a table of overrides in `inherit={}`, any list or table in this
|
||||
list will inherit from previous overrides or the main configuration. The valid
|
||||
options are `"none"` (the default), `"append"`, and `"prepend"`.
|
||||
By default, values in an override replace values from the main configuration or
|
||||
earlier overrides. You can instead [extend a list or table option](#inherit) by
|
||||
setting an `inherit` rule for it.
|
||||
|
||||
#### Examples:
|
||||
|
||||
@@ -173,14 +176,50 @@ This example will provide the command `"pyproject-before && pyproject && pyproje
|
||||
on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"="EGGS"}`.
|
||||
|
||||
|
||||
## Extending existing options {: #inherit }
|
||||
## Option inheritance {: #inherit }
|
||||
|
||||
In the TOML configuration, you can choose how tables and lists are inherited.
|
||||
By default, all values are overridden completely (`"none"`) but sometimes you'd
|
||||
rather `"append"` or `"prepend"` to an existing list or table. You can do this
|
||||
with the `inherit` table in overrides. For example, if you want to add an environment
|
||||
variable for CPython 3.11, without `inherit` you'd have to repeat all the
|
||||
original environment variables in the override. With `inherit`, it's just:
|
||||
As cibuildwheel reads its configuration, each layer normally replaces the value
|
||||
from the previous layer. The layers, from lowest to highest precedence, are:
|
||||
|
||||
1. cibuildwheel's defaults
|
||||
2. `[tool.cibuildwheel]`
|
||||
3. `[tool.cibuildwheel.<platform>]`
|
||||
4. matching `[[tool.cibuildwheel.overrides]]` entries, in order
|
||||
5. `CIBW_<OPTION>`
|
||||
6. `CIBW_<OPTION>_<PLATFORM>`
|
||||
|
||||
For list and table options, you can use an `inherit` rule to merge a value with
|
||||
the value accumulated from the preceding layers instead. The available rules
|
||||
are `"none"` (replace the previous value, the default), `"append"`, and
|
||||
`"prepend"`.
|
||||
|
||||
In `pyproject.toml`, set the rule in the same table as the value it applies to.
|
||||
For example, this adds Twine checks to the default audit configuration:
|
||||
|
||||
```toml
|
||||
[tool.cibuildwheel]
|
||||
inherit.audit-requires = "append"
|
||||
inherit.audit-command = "append"
|
||||
audit-requires = ["twine"]
|
||||
audit-command = "twine check {wheel}"
|
||||
```
|
||||
|
||||
Inheritance can also combine global and platform-specific configuration. This
|
||||
example runs a Linux-specific setup command before the global command:
|
||||
|
||||
```toml
|
||||
[tool.cibuildwheel]
|
||||
before-all = "make -C third_party_lib"
|
||||
|
||||
[tool.cibuildwheel.linux]
|
||||
inherit.before-all = "prepend"
|
||||
before-all = "yum install -y libffi-devel"
|
||||
```
|
||||
|
||||
The same mechanism remains available in overrides. For example, if you want to
|
||||
add an environment variable for CPython 3.11, without `inherit` you'd have to
|
||||
repeat all the original environment variables in the override. With `inherit`,
|
||||
it's just:
|
||||
|
||||
```toml
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
@@ -208,7 +247,23 @@ repair-wheel-command = "echo 'After repair'"
|
||||
```
|
||||
|
||||
As seen in this example, you can have multiple overrides match - they match top
|
||||
to bottom, with the config being accumulated. If you need platform-specific
|
||||
inheritance, you can use `select = "*-????linux_*"` for Linux, `select =
|
||||
"*-win_*"` for Windows, and `select = "*-macosx_*"` for macOS. As always,
|
||||
environment variables will completely override any TOML configuration.
|
||||
to bottom, with the config being accumulated.
|
||||
|
||||
For environment variables, specify the rules in `CIBW_INHERIT`. Rules are
|
||||
separated by semicolons and use lowercase option names. A rule without an
|
||||
explicit value defaults to `append`:
|
||||
|
||||
```yaml
|
||||
CIBW_AUDIT_REQUIRES: twine
|
||||
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||
CIBW_INHERIT: "audit-requires; audit-command"
|
||||
```
|
||||
|
||||
To control a platform-specific environment variable, add the lowercase platform
|
||||
suffix to the option name. For example, this prepends `CIBW_BEFORE_ALL_LINUX` to
|
||||
the value accumulated from the lower-precedence layers:
|
||||
|
||||
```yaml
|
||||
CIBW_BEFORE_ALL_LINUX: yum install -y libffi-devel
|
||||
CIBW_INHERIT: "before-all-linux: prepend"
|
||||
```
|
||||
|
||||
+48
-3
@@ -102,6 +102,51 @@ Meson generally works well with cibuildwheel, but there are a few things to be a
|
||||
|
||||
- If you need to build 32-bit Windows wheels, you need to activate a 32-bit compiler toolchain before starting cibuildwheel. Many users use [ilammy/msvc-dev-cmd](https://github.com/ilammy/msvc-dev-cmd) for this purpose.
|
||||
|
||||
### Caching cibuildwheel's downloaded tools {: #caching}
|
||||
|
||||
To speed up builds, cibuildwheel caches the tools it downloads — CPython/PyPy installers, [`virtualenv`][virtualenv], [python-build-standalone][pbs] archives used on Android/iOS, etc. The active cache folder is printed in the preamble of every run:
|
||||
|
||||
```
|
||||
Cache folder: /Users/Matt/Library/Caches/cibuildwheel
|
||||
```
|
||||
|
||||
By default it lives under the OS user-cache directory:
|
||||
|
||||
| Platform | Default cache folder |
|
||||
| ------------ | --------------------------------------------- |
|
||||
| Linux | `~/.cache/cibuildwheel` |
|
||||
| macOS / iOS | `~/Library/Caches/cibuildwheel` |
|
||||
| Windows | `%LOCALAPPDATA%\pypa\cibuildwheel\Cache` |
|
||||
|
||||
Set the `CIBW_CACHE_PATH` environment variable to point cibuildwheel at a different folder. On CI you'll typically want a workflow-defined path so that the runner's cache action can persist it between runs.
|
||||
|
||||
#### Persisting the cache on GitHub Actions
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ runner.temp }}/cibw-cache
|
||||
key: cibw-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}
|
||||
restore-keys: |
|
||||
cibw-${{ runner.os }}-
|
||||
|
||||
- uses: pypa/cibuildwheel@v3.4.1
|
||||
env:
|
||||
CIBW_CACHE_PATH: ${{ runner.temp }}/cibw-cache
|
||||
```
|
||||
|
||||
The `restore-keys` fallback lets a slightly stale cache still be reused if `pyproject.toml` changes. Adjust the cache key to whatever set of inputs determines what cibuildwheel will download (e.g. include a hash of `pyproject.toml`'s `[tool.cibuildwheel]` section, or pin on a Python build-tools version).
|
||||
|
||||
For platform-specific notes (e.g. caching the official python.org installers on macOS, or NuGet CPython downloads on Windows), see the [platforms documentation](platforms.md).
|
||||
|
||||
If the cache becomes stale or corrupt, run `cibuildwheel --clean-cache` (or simply delete the folder) before re-running.
|
||||
|
||||
!!! warning "Cache poisoning security risk"
|
||||
Use of caching in a release pipeline means the cache folder is now a possible security risk - an attacker could [poison the cache](https://hivesecurity.gitlab.io/blog/github-actions-cache-poisoning-supply-chain/) with executables they have compromised. If you use this for release builds, consider who has access to modify the cache. Specifically be careful if your repo has any workflows using `pull_request_target`, even if they appear unrelated.
|
||||
|
||||
[virtualenv]: https://virtualenv.pypa.io/
|
||||
[pbs]: https://gregoryszorc.com/docs/python-build-standalone/main/
|
||||
|
||||
### Automatic updates using Dependabot {: #automatic-updates}
|
||||
|
||||
Selecting a moving target (like the latest release) is generally a bad idea in CI. If something breaks, you can't tell whether it was your code or an upstream update that caused the breakage, and in a worst-case scenario, it could occur during a release.
|
||||
@@ -113,7 +158,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@v4.0.0
|
||||
uses: pypa/cibuildwheel@v4.1.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.
|
||||
@@ -138,7 +183,7 @@ The second option, and the only one that supports other CI systems, is using a `
|
||||
|
||||
```bash
|
||||
# requirements-cibw.txt
|
||||
cibuildwheel==4.0.0
|
||||
cibuildwheel==4.1.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:
|
||||
@@ -357,7 +402,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==4.0.0 --output-dir wheelhouse
|
||||
pipx run cibuildwheel==4.1.0 --output-dir wheelhouse
|
||||
pipx run twine upload wheelhouse/*.whl
|
||||
```
|
||||
|
||||
|
||||
+36
-5
@@ -593,6 +593,12 @@ You must use this variable to pass variables to Linux builds, since they execute
|
||||
You can use `$PATH` syntax to insert other variables, or the `$(pwd)` syntax to insert the output of other shell commands.
|
||||
Variables are evaluated in the order they appear. Any variable referenced before it is set will evaluate to an empty string.
|
||||
|
||||
The environment seen by the build and test steps starts with the build
|
||||
environment. On Linux, this is the container environment, plus any variables
|
||||
passed through with [`environment-pass`](#environment-pass). On other platforms,
|
||||
the host environment is already available. Assignments in `environment` are
|
||||
evaluated after that base environment and replace any duplicate variable names.
|
||||
|
||||
To specify more than one environment variable, separate the assignments by spaces.
|
||||
|
||||
Platform-specific environment variables are also available:<br/>
|
||||
@@ -728,6 +734,17 @@ The placeholder `{package}` can be used here; it will be replaced by the path to
|
||||
|
||||
On Windows and macOS, the version of Python available inside `before-all` is whatever is available on the host machine. On Linux, a modern Python version is available on PATH.
|
||||
|
||||
!!! note "Shell selection"
|
||||
|
||||
Shell command options such as `before-all`, `before-build`,
|
||||
`repair-wheel-command`, and `before-test` are executed by a platform shell.
|
||||
Linux builds run inside the build container and invoke the container's `sh -c`.
|
||||
On macOS and Windows, cibuildwheel calls Python's
|
||||
`subprocess.run(..., shell=True)`, which normally means `/bin/sh` on macOS
|
||||
and `cmd.exe` on Windows. If you need a specific interpreter, invoke it
|
||||
explicitly, for example `python scripts/prepare.py` or
|
||||
`bash scripts/prepare.sh`.
|
||||
|
||||
This option has special behavior in the overrides section in `pyproject.toml`.
|
||||
On linux, overriding it triggers a new container launch. It cannot be overridden
|
||||
on macOS and Windows.
|
||||
@@ -790,9 +807,9 @@ instead.
|
||||
### `before-build` {: #before-build env-var toml}
|
||||
> Execute a shell command preparing each wheel's build
|
||||
|
||||
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the `pip wheel` command. This is useful if you need to set up some dependency so it's available during the build.
|
||||
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the wheel is built. This is useful if you need to set up some dependency so it's available during the build.
|
||||
|
||||
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as Alpine or ClearLinux users) will still benefit.
|
||||
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as BSD operating system variants) will still benefit.
|
||||
|
||||
The active Python binary can be accessed using `python`, and pip with `pip`; cibuildwheel makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by cibuildwheel.
|
||||
|
||||
@@ -1427,10 +1444,11 @@ Run shell commands to verify your wheels once they are built. Multiple commands
|
||||
audit-requires = ["twine"]
|
||||
audit-command = "twine check {wheel}"
|
||||
|
||||
# Add an additional audit command using overrides, keeping the default abi3audit check
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
select = "*"
|
||||
# Add an additional audit command, keeping the default abi3audit check
|
||||
[tool.cibuildwheel]
|
||||
inherit.audit-requires = "append"
|
||||
inherit.audit-command = "append"
|
||||
audit-requires = ["twine"]
|
||||
audit-command = "twine check {wheel}"
|
||||
```
|
||||
|
||||
@@ -1446,6 +1464,11 @@ Run shell commands to verify your wheels once they are built. Multiple commands
|
||||
# Use twine check to validate wheel metadata
|
||||
CIBW_AUDIT_REQUIRES: "twine"
|
||||
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||
|
||||
# Add twine check to the default audit configuration
|
||||
CIBW_AUDIT_REQUIRES: "twine"
|
||||
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||
CIBW_INHERIT: "audit-requires; audit-command"
|
||||
```
|
||||
|
||||
|
||||
@@ -1653,6 +1676,10 @@ Platform-specific environment variables are also available:<br/>
|
||||
# Install specific versions of test dependencies
|
||||
[tool.cibuildwheel]
|
||||
test-requires = ["pytest==8.2.2", "packaging==24.1"]
|
||||
|
||||
# Dependency specifiers with environment markers are supported
|
||||
[tool.cibuildwheel]
|
||||
test-requires = ["pytest", "pyzstd; python_version >= '3.14'"]
|
||||
```
|
||||
|
||||
In configuration files, you can use an array, and the items will be joined with a space.
|
||||
@@ -1665,6 +1692,10 @@ Platform-specific environment variables are also available:<br/>
|
||||
|
||||
# Install specific versions of test dependencies
|
||||
CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1
|
||||
|
||||
# Use shell-style quoting around specifiers containing spaces, such as
|
||||
# those with environment markers
|
||||
CIBW_TEST_REQUIRES: "pytest 'pyzstd; python_version >= \"3.14\"'"
|
||||
```
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -169,7 +169,7 @@ You must target pyodide with `--platform pyodide` (or use `--only` on the identi
|
||||
|
||||
### Choosing a Pyodide version {: #pyodide-choosing-a-version}
|
||||
|
||||
It is also possible to target a specific Pyodide version by setting the [`pyodide-version`](options.md#pyodide-version) option to the desired version. Users are responsible for setting an appropriate Pyodide version according to the `pyodide-build` version. A list is available in Pyodide's [cross-build environments metadata file](https://github.com/pyodide/pyodide/blob/main/pyodide-cross-build-environments.json), which can be viewed more easily by installing `pyodide-build` from PyPI and using `pyodide xbuildenv search --all` to see a compatibility table.
|
||||
It is also possible to target a specific Pyodide version by setting the [`pyodide-version`](options.md#pyodide-version) option to the desired version. Users are responsible for setting an appropriate Pyodide version according to the `pyodide-build` version. A list is available in Pyodide's [cross-build environments metadata file](https://pyodide.github.io/pyodide/api/v2/pyodide-cross-build-environments.json), which can be viewed more easily by installing `pyodide-build` from PyPI and using `pyodide xbuildenv search --all` to see a compatibility table.
|
||||
|
||||
If there are pre-releases available for a newer Pyodide version, the `pyodide-prerelease` [`enable`](options.md#enable) can be used to include pre-release versions. To build for older Pyodide versions that are no longer the current stable, use the `pyodide-eol` [`enable`](options.md#enable).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
- bash: |
|
||||
set -o errexit
|
||||
python3 -m pip install --upgrade pip
|
||||
pip3 install cibuildwheel==4.0.0
|
||||
pip3 install cibuildwheel==4.1.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==4.0.0
|
||||
python3 -m pip install cibuildwheel==4.1.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==4.0.0
|
||||
pip install cibuildwheel==4.1.0
|
||||
displayName: Install dependencies
|
||||
- bash: cibuildwheel --output-dir wheelhouse .
|
||||
displayName: Build wheels
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- run:
|
||||
name: Build the Linux wheels.
|
||||
command: |
|
||||
python3 -m pip install --user cibuildwheel==4.0.0
|
||||
python3 -m pip install --user cibuildwheel==4.1.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==4.0.0
|
||||
python3 -m pip install --user cibuildwheel==4.1.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 x86_64/universal2 tests
|
||||
pip3 install cibuildwheel==4.0.0
|
||||
pip3 install cibuildwheel==4.1.0
|
||||
cibuildwheel --output-dir wheelhouse
|
||||
- store_artifacts:
|
||||
path: wheelhouse/
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v4.0.0
|
||||
uses: pypa/cibuildwheel@v4.1.0
|
||||
env:
|
||||
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
|
||||
CIBW_ARCHS: ${{ matrix.archs || 'auto' }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v4.0.0
|
||||
uses: pypa/cibuildwheel@v4.1.0
|
||||
# env:
|
||||
# CIBW_SOME_OPTION: value
|
||||
# ...
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
run: pipx run cibuildwheel==4.0.0
|
||||
run: pipx run cibuildwheel==4.1.0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
platforms: all
|
||||
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v4.0.0
|
||||
uses: pypa/cibuildwheel@v4.1.0
|
||||
env:
|
||||
# configure cibuildwheel on Linux to build native archs ('auto'),
|
||||
# and to split the remaining architectures between the x86_64 and
|
||||
|
||||
@@ -12,7 +12,7 @@ linux:
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
script:
|
||||
- curl -sSL https://get.docker.com/ | sh
|
||||
- python -m pip install cibuildwheel==4.0.0
|
||||
- python -m pip install cibuildwheel==4.1.0
|
||||
- cibuildwheel --output-dir wheelhouse
|
||||
artifacts:
|
||||
paths:
|
||||
@@ -23,7 +23,7 @@ windows:
|
||||
before_script:
|
||||
- choco install python -y --allow-downgrade --version 3.12.4
|
||||
- choco install git.install -y
|
||||
- py -m pip install cibuildwheel==4.0.0
|
||||
- py -m pip install cibuildwheel==4.1.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==4.0.0
|
||||
- python3 -m pip install cibuildwheel==4.1.0
|
||||
script:
|
||||
- python3 -m cibuildwheel --output-dir wheelhouse
|
||||
artifacts:
|
||||
|
||||
@@ -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==4.0.0
|
||||
- python -m pip install cibuildwheel==4.1.0
|
||||
# Assuming your CI runner's default architecture is x86_64...
|
||||
- cibuildwheel --output-dir wheelhouse --platform linux --archs aarch64
|
||||
artifacts:
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "cibuildwheel"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0"
|
||||
description = "Build Python wheels on CI with minimal configuration."
|
||||
readme = "README.md"
|
||||
license = "BSD-2-Clause"
|
||||
|
||||
@@ -49,10 +49,10 @@ def test_abi3(tmp_path: Path) -> None:
|
||||
project_dir,
|
||||
add_env={
|
||||
# free_threaded, GraalPy, and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
|
||||
# pyodide uses cp313 (the stable version) which supports limited API / abi3
|
||||
# pyodide uses cp314 (the latest stable version) which supports limited API / abi3
|
||||
# also limit the number of builds for test performance reasons
|
||||
"CIBW_BUILD": (
|
||||
"cp313-*"
|
||||
"cp314-*"
|
||||
if utils.get_platform() == "pyodide"
|
||||
else "cp39-* cp310-* pp310-* gp312_250-* cp312-* cp314t-*"
|
||||
),
|
||||
|
||||
@@ -405,7 +405,11 @@ def test_environment_markers(tmp_path: Path) -> None:
|
||||
**cp313_env,
|
||||
"CIBW_TEST_COMMAND": f"python -m pytest {test_filename}",
|
||||
"CIBW_TEST_SOURCES": test_filename,
|
||||
"CIBW_TEST_REQUIRES": "pytest certifi;sys_platform=='android' platformdirs;sys_platform!='android'",
|
||||
"CIBW_TEST_REQUIRES": (
|
||||
"pytest"
|
||||
" 'certifi; sys_platform == \"android\"'"
|
||||
" 'platformdirs; sys_platform != \"android\"'"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -87,13 +87,13 @@ def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]:
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("python_version", ["3.9", "3.13"])
|
||||
@pytest.mark.parametrize("python_version", ["3.9", "3.14"])
|
||||
def test_pinned_versions(
|
||||
tmp_path: Path, python_version: str, build_frontend_env_nouv: dict[str, str]
|
||||
) -> None:
|
||||
if utils.get_platform() == "linux":
|
||||
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
|
||||
if python_version != "3.13" and utils.get_platform() == "pyodide":
|
||||
if python_version != "3.14" and utils.get_platform() == "pyodide":
|
||||
pytest.skip(f"pyodide does not support Python {python_version}")
|
||||
|
||||
project_dir = tmp_path / "project"
|
||||
|
||||
@@ -175,6 +175,6 @@ def test_pyodide_repair_wheel(tmp_path: Path) -> None:
|
||||
|
||||
# check that the expected wheels are produced
|
||||
expected_wheels = [
|
||||
"spam-0.1.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl",
|
||||
"spam-0.1.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl",
|
||||
]
|
||||
assert set(actual_wheels) == set(expected_wheels)
|
||||
|
||||
+6
-6
@@ -31,7 +31,7 @@ EMULATED_ARCHS: Final[list[str]] = sorted(
|
||||
PYPY_ARCHS = ["x86_64", "i686", "AMD64", "aarch64", "arm64"]
|
||||
GRAALPY_ARCHS = ["x86_64", "AMD64", "aarch64", "arm64"]
|
||||
|
||||
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 13)
|
||||
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 14)
|
||||
|
||||
# temporary workaround: set by build_frontend_env fixture to skip graalpy
|
||||
# when uv is the build frontend (compatibility issue between graalpy and uv)
|
||||
@@ -278,9 +278,9 @@ def _expected_wheels(
|
||||
|
||||
# To be kept in sync with Python versions for Pyodide identifiers in cibuildwheel/selector.py.
|
||||
if platform == "pyodide" and python_abi_tags is None:
|
||||
python_abi_tags = ["cp313-cp313"]
|
||||
if EnableGroup.PyodidePrerelease in enable_groups:
|
||||
python_abi_tags.append("cp314-cp314")
|
||||
python_abi_tags = ["cp313-cp313", "cp314-cp314"]
|
||||
if EnableGroup.PyodideEoL in enable_groups:
|
||||
python_abi_tags.insert(0, "cp312-cp312")
|
||||
elif (platform == "android" and python_abi_tags is None) or (
|
||||
platform == "ios" and python_abi_tags is None
|
||||
):
|
||||
@@ -414,8 +414,8 @@ def _expected_wheels(
|
||||
|
||||
if not platform_tags:
|
||||
# for example if the python tag is `none` or `abi3`, the wheel
|
||||
# is built by the stable cp313 Python version
|
||||
platform_tags = ["pyemscripten_2025_0_wasm32"]
|
||||
# is built by the latest stable cp314 Python version
|
||||
platform_tags = ["pyemscripten_2026_0_wasm32"]
|
||||
|
||||
else:
|
||||
msg = f"Unsupported platform {platform!r}"
|
||||
|
||||
@@ -7,6 +7,7 @@ import sys
|
||||
import pytest
|
||||
|
||||
import cibuildwheel.architecture
|
||||
import cibuildwheel.errors
|
||||
from cibuildwheel.architecture import Architecture, arch_synonym
|
||||
|
||||
TYPE_CHECKING = False
|
||||
@@ -142,3 +143,33 @@ def test_arch_synonym(
|
||||
arch: str, from_platform: PlatformName, to_platform: PlatformName, expected: str | None
|
||||
) -> None:
|
||||
assert arch_synonym(arch, from_platform, to_platform) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "platform", "expected"),
|
||||
[
|
||||
# Case-insensitive, platform-resolved names (issue #2373).
|
||||
("arm64", "windows", {Architecture.ARM64}),
|
||||
("ARM64", "windows", {Architecture.ARM64}),
|
||||
("amd64", "windows", {Architecture.AMD64}),
|
||||
("AMD64", "windows", {Architecture.AMD64}),
|
||||
("X86", "windows", {Architecture.x86}),
|
||||
("x86", "windows", {Architecture.x86}),
|
||||
# macOS resolves the lowercase arm64 even when given uppercase.
|
||||
("ARM64", "macos", {Architecture.arm64}),
|
||||
("arm64", "macos", {Architecture.arm64}),
|
||||
("X86_64", "macos", {Architecture.x86_64}),
|
||||
# Multiple, mixed-case archs in one config string.
|
||||
("arm64 amd64", "windows", {Architecture.ARM64, Architecture.AMD64}),
|
||||
],
|
||||
)
|
||||
def test_arch_parse_config_case_insensitive(
|
||||
config: str, platform: PlatformName, expected: set[Architecture]
|
||||
) -> None:
|
||||
assert Architecture.parse_config(config, platform) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ["windows", "macos", "linux"])
|
||||
def test_arch_parse_config_invalid(platform: PlatformName) -> None:
|
||||
with pytest.raises(cibuildwheel.errors.ConfigurationError):
|
||||
Architecture.parse_config("nonexistent", platform)
|
||||
|
||||
@@ -91,14 +91,15 @@ def test_build_filter_pypy_all() -> None:
|
||||
assert build_selector("pp39-manylinux_x86_64")
|
||||
|
||||
|
||||
def test_build_filter_pyodide_prerelease() -> None:
|
||||
build_selector = BuildSelector(
|
||||
build_config="*",
|
||||
skip_config="",
|
||||
enable=frozenset([EnableGroup.PyodidePrerelease]),
|
||||
)
|
||||
assert build_selector("cp313-pyodide_wasm32")
|
||||
assert build_selector("cp314-pyodide_wasm32")
|
||||
# Re-enable when we have Pyodide 3.15 prerelease builds to test against
|
||||
# def test_build_filter_pyodide_prerelease() -> None:
|
||||
# build_selector = BuildSelector(
|
||||
# build_config="*",
|
||||
# skip_config="",
|
||||
# enable=frozenset([EnableGroup.PyodidePrerelease]),
|
||||
# )
|
||||
# assert build_selector("cp314-pyodide_wasm32")
|
||||
# assert build_selector("cp315-pyodide_wasm32")
|
||||
|
||||
|
||||
def test_build_filter_pyodide() -> None:
|
||||
@@ -108,8 +109,8 @@ def test_build_filter_pyodide() -> None:
|
||||
enable=frozenset(),
|
||||
)
|
||||
assert build_selector("cp313-pyodide_wasm32")
|
||||
assert build_selector("cp314-pyodide_wasm32")
|
||||
assert not build_selector("cp312-pyodide_wasm32")
|
||||
assert not build_selector("cp314-pyodide_wasm32")
|
||||
|
||||
|
||||
def test_build_filter_pyodide_eol() -> None:
|
||||
@@ -120,7 +121,7 @@ def test_build_filter_pyodide_eol() -> None:
|
||||
)
|
||||
assert build_selector("cp312-pyodide_wasm32")
|
||||
assert build_selector("cp313-pyodide_wasm32")
|
||||
assert not build_selector("cp314-pyodide_wasm32")
|
||||
assert build_selector("cp314-pyodide_wasm32")
|
||||
|
||||
|
||||
def test_skip() -> None:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Tests for reading and resolving configuration options."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
@@ -5,6 +7,7 @@ from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.options import (
|
||||
EnvironmentFormat,
|
||||
InheritRule,
|
||||
@@ -472,7 +475,11 @@ test-command = "pyproject-override"
|
||||
)
|
||||
|
||||
with pytest.raises(OptionsReaderError):
|
||||
OptionsReader(config_file_path=pyproject_toml, platform=cast("Any", platform), env={})
|
||||
print(
|
||||
OptionsReader(
|
||||
config_file_path=pyproject_toml, platform=cast("Any", platform), env={}
|
||||
).overrides
|
||||
)
|
||||
|
||||
|
||||
def test_config_settings(tmp_path: Path) -> None:
|
||||
@@ -549,6 +556,112 @@ before-all = ["override2"]
|
||||
)
|
||||
|
||||
|
||||
def test_global_inherit(tmp_path: Path) -> None:
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""\
|
||||
[tool.cibuildwheel]
|
||||
inherit = {audit-command = "append"}
|
||||
audit-command = "twine check {wheel}"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||
|
||||
assert (
|
||||
options_reader.get("audit-command", option_format=ListFormat(" && "))
|
||||
== "abi3audit --strict --report {abi3_wheel} && twine check {wheel}"
|
||||
)
|
||||
|
||||
|
||||
def test_platform_inherit(tmp_path: Path) -> None:
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""\
|
||||
[tool.cibuildwheel]
|
||||
before-all = "global"
|
||||
|
||||
[tool.cibuildwheel.linux]
|
||||
inherit = {before-all = "prepend"}
|
||||
before-all = "linux"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||
|
||||
assert options_reader.get("before-all", option_format=ListFormat(" && ")) == "linux && global"
|
||||
|
||||
|
||||
def test_environment_inherit(tmp_path: Path) -> None:
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""\
|
||||
[tool.cibuildwheel]
|
||||
before-all = "config"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml,
|
||||
platform="linux",
|
||||
env={
|
||||
"CIBW_BEFORE_ALL": "env",
|
||||
"CIBW_BEFORE_ALL_LINUX": "linux-env",
|
||||
# A key without a value defaults to append.
|
||||
"CIBW_INHERIT": "before-all; before-all-linux: prepend",
|
||||
},
|
||||
)
|
||||
|
||||
assert (
|
||||
options_reader.get("before-all", option_format=ListFormat(" && "))
|
||||
== "linux-env && config && env"
|
||||
)
|
||||
|
||||
|
||||
def test_environment_inherit_none_overrides_default_rule(tmp_path: Path) -> None:
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""\
|
||||
[tool.cibuildwheel]
|
||||
enable = ["cpython-freethreading"]
|
||||
"""
|
||||
)
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml,
|
||||
platform="linux",
|
||||
env={"CIBW_ENABLE": "pypy", "CIBW_INHERIT": "enable: none"},
|
||||
)
|
||||
|
||||
assert (
|
||||
options_reader.get(
|
||||
"enable", option_format=ListFormat(" "), default_env_rule=InheritRule.APPEND
|
||||
)
|
||||
== "pypy"
|
||||
)
|
||||
|
||||
|
||||
def test_invalid_config_inherit_rule(tmp_path: Path) -> None:
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""\
|
||||
[tool.cibuildwheel]
|
||||
inherit = {before-all = "invalid"}
|
||||
"""
|
||||
)
|
||||
|
||||
with pytest.raises(OptionsReaderError, match="must contain only"):
|
||||
OptionsReader(pyproject_toml, platform="linux", env={})
|
||||
|
||||
|
||||
def test_invalid_environment_inherit_rule() -> None:
|
||||
options_reader = OptionsReader(platform="linux", env={"CIBW_INHERIT": "before-all: invalid"})
|
||||
|
||||
with pytest.raises(
|
||||
errors.ConfigurationError, match="Failed to parse CIBW_INHERIT environment variable"
|
||||
):
|
||||
options_reader.get("before-all", option_format=ListFormat(" && "))
|
||||
|
||||
|
||||
def test_audit_command_option(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
@@ -25,12 +25,19 @@ from cibuildwheel.options import (
|
||||
_get_pinned_container_images,
|
||||
)
|
||||
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
|
||||
from cibuildwheel.platforms.pyodide import (
|
||||
PyodideXBuildEnvInfo,
|
||||
validate_pyodide_target_python,
|
||||
)
|
||||
from cibuildwheel.platforms.pyodide import (
|
||||
PythonConfiguration as PyodidePythonConfiguration,
|
||||
)
|
||||
from cibuildwheel.util import resources
|
||||
from cibuildwheel.util.packaging import DependencyConstraints
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
PYPROJECT_1 = """
|
||||
[tool.cibuildwheel]
|
||||
@@ -106,6 +113,91 @@ def test_options_1(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert local.pyodide_version == "0.29.4"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_pyodide_xbuildenv_info() -> Callable[[str, str], PyodideXBuildEnvInfo]:
|
||||
def _make(version: str, python: str) -> PyodideXBuildEnvInfo:
|
||||
return {
|
||||
"version": version,
|
||||
"python": python,
|
||||
"emscripten": "5.0.3",
|
||||
"pyodide_build": {"min": None, "max": None},
|
||||
"compatible": True,
|
||||
}
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_pyodide_python_configuration() -> Callable[[str, str], PyodidePythonConfiguration]:
|
||||
def _make(identifier: str, version: str) -> PyodidePythonConfiguration:
|
||||
return PyodidePythonConfiguration(
|
||||
version=version,
|
||||
identifier=identifier,
|
||||
default_pyodide_version="0.0.0",
|
||||
node_version="v22",
|
||||
)
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
def test_validate_pyodide_target_python_matching(
|
||||
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
|
||||
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
|
||||
) -> None:
|
||||
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
|
||||
config = make_pyodide_python_configuration("cp314-pyodide_wasm32", "3.14")
|
||||
# should not raise
|
||||
validate_pyodide_target_python(xbuildenv_info, config)
|
||||
|
||||
|
||||
def test_validate_pyodide_target_python_mismatch(
|
||||
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
|
||||
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
|
||||
) -> None:
|
||||
# a Python 3.14 xbuildenv applied to a cp313-pyodide_wasm32 build, when a global
|
||||
# pyodide-version is set across mismatched targets
|
||||
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
|
||||
config = make_pyodide_python_configuration("cp313-pyodide_wasm32", "3.13")
|
||||
|
||||
with pytest.raises(errors.ConfigurationError) as exc_info:
|
||||
validate_pyodide_target_python(xbuildenv_info, config)
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "cp313-pyodide_wasm32" in message
|
||||
assert "314.0.0" in message
|
||||
assert "3.13" in message
|
||||
assert "3.14" in message
|
||||
|
||||
|
||||
def test_test_and_audit_requires_with_dependency_specifiers(tmp_path: Path) -> None:
|
||||
"""Regression test for https://github.com/pypa/cibuildwheel/issues/2912"""
|
||||
pyproject_toml = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
test-requires = [
|
||||
"pytest",
|
||||
"pyzstd; python_version >= '3.14'",
|
||||
"zarr>=3",
|
||||
]
|
||||
audit-requires = ["abi3audit; python_version >= '3.9'"]
|
||||
"""
|
||||
)
|
||||
|
||||
args = CommandLineArguments.defaults()
|
||||
args.package_dir = tmp_path
|
||||
|
||||
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||
build_options = options.build_options(identifier=None)
|
||||
|
||||
assert build_options.test_requires == [
|
||||
"pytest",
|
||||
"pyzstd; python_version >= '3.14'",
|
||||
"zarr>=3",
|
||||
]
|
||||
assert build_options.audit_requires == ["abi3audit; python_version >= '3.9'"]
|
||||
|
||||
|
||||
def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||
f.write(PYPROJECT_1)
|
||||
|
||||
+53
-11
@@ -11,6 +11,7 @@ from cibuildwheel.util.file import copy_test_sources, remove_on_error
|
||||
from cibuildwheel.util.helpers import (
|
||||
FlexibleVersion,
|
||||
format_safe,
|
||||
parse_arbitrary_key_value_string,
|
||||
parse_key_value_string,
|
||||
prepare_command,
|
||||
unwrap,
|
||||
@@ -218,20 +219,61 @@ def test_parse_key_value_string_unknown_name() -> None:
|
||||
with pytest.raises(ValueError, match=r"Failed to parse 'key: value'. Unknown field name 'key'"):
|
||||
parse_key_value_string("key: value")
|
||||
|
||||
# Unknown fields can be enabled by passing "*".
|
||||
assert parse_key_value_string(
|
||||
"key: value",
|
||||
kw_arg_names=["*"],
|
||||
) == {
|
||||
"key": ["value"],
|
||||
|
||||
def test_parse_arbitrary_key_value_string_basic() -> None:
|
||||
assert parse_arbitrary_key_value_string("before-test: append; after-test: prepend") == {
|
||||
"before-test": ["append"],
|
||||
"after-test": ["prepend"],
|
||||
}
|
||||
|
||||
assert parse_key_value_string(
|
||||
"key1: value1a value1b; key2: value2",
|
||||
kw_arg_names=["*"],
|
||||
|
||||
def test_parse_arbitrary_key_value_string_multiple_values() -> None:
|
||||
assert parse_arbitrary_key_value_string(
|
||||
"package1: some/header.h some/library.a; package2: other/header.h"
|
||||
) == {
|
||||
"key1": ["value1a", "value1b"],
|
||||
"key2": ["value2"],
|
||||
"package1": ["some/header.h", "some/library.a"],
|
||||
"package2": ["other/header.h"],
|
||||
}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_keys_without_values_default() -> None:
|
||||
assert parse_arbitrary_key_value_string(
|
||||
"before-build; before-test: prepend", default_value="append"
|
||||
) == {
|
||||
"before-build": ["append"],
|
||||
"before-test": ["prepend"],
|
||||
}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_keys_without_values_no_default() -> None:
|
||||
with pytest.raises(ValueError, match="No value specified"):
|
||||
parse_arbitrary_key_value_string("before-build")
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_empty() -> None:
|
||||
assert parse_arbitrary_key_value_string("") == {}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_duplicate_keys() -> None:
|
||||
assert parse_arbitrary_key_value_string("key: val1; key: val2") == {
|
||||
"key": ["val1", "val2"],
|
||||
}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_key_only_with_colon() -> None:
|
||||
assert parse_arbitrary_key_value_string("key:") == {"key": []}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_quoted_values() -> None:
|
||||
assert parse_arbitrary_key_value_string('key: "hello world"') == {"key": ["hello world"]}
|
||||
|
||||
|
||||
def test_parse_arbitrary_key_value_string_multiple_bare_keys_with_default() -> None:
|
||||
"""works, but should remain undocumented"""
|
||||
assert parse_arbitrary_key_value_string("a b c", default_value="yes") == {
|
||||
"a": ["yes"],
|
||||
"b": ["yes"],
|
||||
"c": ["yes"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user