Compare commits

..
92 Commits
Author SHA1 Message Date
Joe Rickerby 79b0dd3287 Bump version: v2.21.0 2024-09-13 16:42:42 +01:00
Matthieu Darbois 0787a44d99 fix: enforce minimum version of docker/podman (#1961)
* fix: enforce minimum version of docker/podman

This allows to always pass `--platform` to the OCI engine
thus fixing issues with multiarch images.

* Allow older versions with warnings

* Upgrade docker on Travis CI

* fix: use `docker cp` instead of `tar`

* Enforce docker>=24.0

* move log to include container.copy_into

* fix: travis-ci, only update docker on aarch64

* skip test_multiarch_image on s390x / ppc64le

* skip flaky test

* chore: only install test deps on Travis CI

* fix: do not try to pull images tagged `cibw_local`

* use "--pull=never" for local images

* Use docker image inspect to check if an image needs to be pulled
2024-09-12 15:32:43 -04:00
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>mayeut
fd11286290 [Bot] Update dependencies (#1998)
* Update dependencies

* fix: set minimal deployment target to macOS 10.13 with CPython 3.12

---------

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2024-09-12 15:32:22 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 22dc864d60 [pre-commit.ci] pre-commit autoupdate (#2000)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-10 12:22:54 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9d2f46ce4e chore(deps): bump the actions group with 2 updates (#1999)
Bumps the actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).


Updates `actions/attest-build-provenance` from 1.4.2 to 1.4.3
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/6149ea5740be74af77f260b9db67e633f6b0a9a1...1c608d11d69870c2092266b3f9a6f3abbf17002c)

Updates `peter-evans/create-pull-request` from 6 to 7
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 20:04:37 +01:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> b1ed7bb3bc [Bot] Update dependencies (#1993)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-09-03 17:16:41 -04:00
Joe RickerbyandMatthieu Darbois ca08f5fbcf Customise merge behaviour depending on option. Fix #1803 (#1984)
Fix for #1803, where the method used for merging the config-settings option - string concatenation - was of little use.

I broke out the TableFmt object into a protocol-like object called OptionFormat, where the formatting and merge rules can be customised more deeply. This is more explicit, and lets us be a bit more precise with how TOML object get converted into the options.

I intend to use the flexibility of the OptionsFormat structure to tackle the remaining abiguity around environment variable quoting, as seen in the xfails in the options_test.py unit test and discussed in #1271.


Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>
2024-09-03 16:05:00 +01:00
pre-commit-ci[bot] 229029e112 [pre-commit.ci] pre-commit autoupdate (#1994) 2024-09-03 07:43:40 +01:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 3294577c17 [pre-commit.ci] pre-commit autoupdate (#1987)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)
- [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2)
- [github.com/python-jsonschema/check-jsonschema: 0.29.1 → 0.29.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.1...0.29.2)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-27 12:21:14 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 8f51efbc3e [Bot] Update dependencies (#1985)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-08-26 10:41:21 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ebdbb3cbd5 chore(deps): bump actions/attest-build-provenance from 1.4.1 to 1.4.2 in the actions group (#1986)
chore(deps): bump actions/attest-build-provenance in the actions group

Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/attest-build-provenance` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/310b0a4a3b0b78ef57ecda988ee04b132db73ef8...6149ea5740be74af77f260b9db67e633f6b0a9a1)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 10:40:32 -04:00
pre-commit-ci[bot]pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>Henry Schreiner
3a30bec9ef [pre-commit.ci] pre-commit autoupdate (#1978)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.7 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.7...v0.6.1)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore: ruff python-version not needed

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
2024-08-21 00:34:31 -04:00
Matthieu Darbois 126b81916d chore(ci): podman is now pre-installed on GHA runners (#1974)
podman is now pre-installed on GHA runners
2024-08-19 11:28:24 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 9abea5b73b [Bot] Update dependencies (#1976)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-08-19 11:26:53 -04:00
Matthieu Darbois 170e7aaeb9 fix: TravisCI failures & status (#1970)
* fix: TravisCI failures & status
* Allow failure on s390x
2024-08-15 17:48:24 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 5b570a2451 [Bot] Update dependencies (#1963)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-08-14 16:00:33 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ffd7dddcc7 [pre-commit.ci] pre-commit autoupdate (#1967)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.5.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.5.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-14 16:00:02 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b0b13d3a16 chore(deps): bump actions/attest-build-provenance from 1.4.0 to 1.4.1 in the actions group (#1964)
chore(deps): bump actions/attest-build-provenance in the actions group

Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/attest-build-provenance` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/210c1913531870065f03ce1f9440dd87bc0938cd...310b0a4a3b0b78ef57ecda988ee04b132db73ef8)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 15:59:47 -04:00
Matthieu Darbois b1781e4606 fix: AppVeyor CI tests & sample (#1971) 2024-08-14 15:57:17 -04:00
Henry Schreinerandmayeut 3580374b18 fix: support empty CIBW_PLATFORM (#1969)
* fix: support empty CIBW_PLATFORM

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: support empty CIBW_PLATFORM

* Add test

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2024-08-14 15:56:06 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> c6dd39bd83 [Bot] Update dependencies (#1954)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-08-07 16:08:57 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> dd8fe86308 [pre-commit.ci] pre-commit autoupdate (#1958)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6)
- [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-07 16:07:39 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2e12f05d5e chore(deps): bump actions/attest-build-provenance from 1.3.3 to 1.4.0 in the actions group (#1956)
chore(deps): bump actions/attest-build-provenance in the actions group

Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/attest-build-provenance` from 1.3.3 to 1.4.0
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/5e9cb68e95676991667494a6a4e59b8a2f13e1d0...210c1913531870065f03ce1f9440dd87bc0938cd)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 16:07:28 -04:00
Henry Schreiner bd033a4447 Bump version: v2.20.0 2024-08-03 22:37:32 -04:00
Ewout ter Hoevenpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>mayeut
9d023cf77a Build CPython 3.13 by default (#1950)
* Build CPython 3.13 by default

Enable building with CPython 3.13 by default, no longer requiring prerelease_pythons flag to be set true.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* README.md: Add note about free-threaded support

* build_selector_test.py: cp313t still behind flag

Even with 3.13 being enabled by default, cp313t is still behind a flag for now.

This commit updates the test accordingly.

* fix unit tests

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2024-08-03 14:46:45 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 8a69dbd9d4 [Bot] Update dependencies (#1949)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-08-03 12:05:10 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 147de6f4f7 [pre-commit.ci] pre-commit autoupdate (#1947)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5)
- [github.com/python-jsonschema/check-jsonschema: 0.29.0 → 0.29.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.0...0.29.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-30 10:01:48 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> e217e44661 [Bot] Update dependencies (#1946)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-07-29 11:02:55 -04:00
Matt Richards 871ef6b506 tests: show xfail traceback summaries again (#1943)
Revert "tests: don't print xfails (#1865)"

This reverts commit 8d86d3122b.
2024-07-23 09:14:06 -04:00
pre-commit-ci[bot]pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>Henry Schreiner
fac72f024a [pre-commit.ci] pre-commit autoupdate (#1944)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.2 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.2...v0.5.4)
- [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0)

* chore: type ignore no longer needed

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
2024-07-23 09:26:50 +01:00
Conor MacBrideandMatthieu Darbois d4c332104d feat: add CIBW_ALLOW_EMPTY environment variable (#1937)
Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>
2024-07-23 09:25:09 +01:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 5af5df4e4f [Bot] Update dependencies (#1935)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-07-21 19:56:36 +02:00
Conor MacBride 6c5cf87871 docs: fix nox docs command (#1938) 2024-07-21 11:37:43 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 46f4108f23 chore(deps): bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group (#1936)
chore(deps): bump actions/attest-build-provenance in the actions group

Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/attest-build-provenance` from 1.3.2 to 1.3.3
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/bdd51370e0416ac948727f861e03c2f05d32d78e...5e9cb68e95676991667494a6a4e59b8a2f13e1d0)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 11:29:36 +02:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 1da439f8dc [pre-commit.ci] pre-commit autoupdate (#1939)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2)
- [github.com/python-jsonschema/check-jsonschema: 0.28.6 → 0.29.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.6...0.29.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-21 11:29:11 +02:00
Matthieu Darbois 7af9d039fb fix: do not use uv on Windows PyPy 3.8 (#1941)
fix: do not use uv on PyPy 3.8

uv 0.2.25+ does not work with PyPy 3.8
Given PyPy 3.8 is EOL, just disable uv for this config rather than ask for it to be supported again in uv.
2024-07-21 10:51:43 +02:00
MusicalNinjaDadJoe RickerbyHenry Schreinerpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
de84624d70 fix: set VSCMD_ARG_TGT_ARCH based on targeted architecture (#1876)
* set VSCMD_ARG_TGT_ARCH based on targetted architecture

* only set VSCMD_ARG_TGT_ARCH if not already set

* add unit tests to check setuptools correctly identifies windows arch

* only run tests on windows

* arm64 fails on azure pipelines (only)

* Update windows.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

Co-authored-by: Joe Rickerby <joerick@mac.com>

* update test_env_set to validate FatalError is raised on env collision

* remove noqa ARG001 (no longer needed)

---------

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Joe Rickerby <joerick@mac.com>
2024-07-12 14:32:42 -04:00
Matthieu Darbois cac121a67d chore: update examples to use python 3.12 (#1919) 2024-07-09 09:15:44 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 604da878cb [Bot] Update dependencies (#1923)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-07-09 09:14:32 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> eaf0051162 chore(deps): bump actions/attest-build-provenance from 1.1.2 to 1.3.2 in the actions group (#1925)
chore(deps): bump actions/attest-build-provenance in the actions group

Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/attest-build-provenance` from 1.1.2 to 1.3.2
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/173725a1209d09b31f9d30a3890cf2757ebbff0d...bdd51370e0416ac948727f861e03c2f05d32d78e)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 09:14:08 -04:00
Mike Taves 8dcf71e02d DOC: fix link to correct PR number (#1928) 2024-07-09 09:13:20 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 92f0142a3e [pre-commit.ci] pre-commit autoupdate (#1927)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-09 09:13:04 -04:00
Matthieu Darbois 6c8347f465 chore: use python from setup-python step to run nox (#1921) 2024-07-08 07:59:44 +02:00
Matthieu Darbois a4d24056ef chore: move AlreadyBuiltWheelError & NonPlatformWheelError to errors.py (#1920) 2024-07-07 09:47:03 +02:00
Henry Schreiner 7e5a838a63 Bump version: v2.19.2 2024-07-02 08:07:58 -04:00
Henry Schreiner 19e1b8a4b0 chore: attestations (#1916) 2024-07-02 07:55:40 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 73581ae579 [Bot] Update dependencies (#1917)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-07-02 07:55:10 -04:00
pre-commit-ci[bot]pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>Henry Schreiner
f21ff5e84c [pre-commit.ci] pre-commit autoupdate (#1914)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0)
- [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1)

* style follow suggestion

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-07-01 22:08:31 -04:00
Henry Schreiner 973946b172 fix: support --no-isolation with build[uv] (#1889)
* fix: support --no-isolation with build[uv]

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* docs: mention caveat

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-07-01 20:15:09 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 5ea40e6ff3 [Bot] Update dependencies (#1913)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-07-01 10:58:15 -04:00
Matthieu Darbois 6a36f6494b feat: add macOS on GitLab (#1911) 2024-06-30 10:59:58 +02:00
Matthieu Darbois 31de15bcd9 chore: remove nosetests from tests and docs (#1898)
fix #499
2024-06-30 10:21:33 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 3179fd215a [Bot] Update dependencies (#1900)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-06-30 08:07:29 +02:00
Matthieu Darbois 184d4e1280 fix(ci): CircleCI / Azure Pipelines / GitLab (#1899)
* fix(ci): CircleCI

CircleCI does not support Intel macOS runners anymore.
c.f. https://discuss.circleci.com/t/macos-intel-support-deprecation-in-january-2024/48718

* fix(ci): Azure Pipelines

* fix(ci): GitLab
2024-06-29 14:12:13 -04:00
Eric LarsonandMatthieu Darbois 08165ffeed DOC: Document macOS 3.8 arm64 workaround (#1871)
* BUG: Fix bug with macOS 3.8 arm64 URL

* TST: Ping

* DOC: Update

* FIX: Remove old

* DOC: Both

* Apply suggestions from code review

Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>

---------

Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>
2024-06-27 11:52:32 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 9bb03835e5 [pre-commit.ci] pre-commit autoupdate (#1895)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10)
- [github.com/python-jsonschema/check-jsonschema: 0.28.5 → 0.28.6](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.5...0.28.6)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-26 16:16:34 -04:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 2552303ff3 [Bot] Update dependencies (#1894)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-06-25 07:54:08 +02:00
Matthieu Darbois 5b74582a72 chore: remove pip beta workaround (#1892) 2024-06-23 14:16:44 +02:00
Matthieu Darbois 4470eb0dd9 chore: remove pip beta workaround (#1891) 2024-06-22 14:38:46 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 8bda260d3a [Bot] Update dependencies (#1885)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-06-22 10:57:56 +02:00
Lukas Burgholzer 5b5a95bdc2 docs: properly escape extra in build[uv] (#1886) 2024-06-19 08:01:35 +02:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 561fa32683 [pre-commit.ci] pre-commit autoupdate (#1888)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9)
- [github.com/python-jsonschema/check-jsonschema: 0.28.4 → 0.28.5](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.4...0.28.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-18 15:17:45 -04:00
Matthieu Darbois 70913f0a49 chore: remove cached_property from utils (#1884) 2024-06-17 23:01:08 +02:00
Siddhant Goel f49397f151 docs: add streaming-form-data to projects.yml (#1883) 2024-06-17 09:43:38 -04:00
Matthieu Darbois c1b8a12cd5 chore(tests): run a project with tests using build[uv] frontend (#1880) 2024-06-17 01:07:35 -04:00
Henry Schreiner 932529cab1 Bump version: v2.19.1 2024-06-13 02:38:27 -04:00
Henry Schreiner 70fb1c4a1a fix: specify full path to non-venv Python (#1881)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-06-13 08:18:09 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 405a475ac7 [Bot] Update dependencies (#1879)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-06-12 23:16:49 -04:00
Nathan Goldbaum 78da7bafcc docs: update README.md (#1874) 2024-06-12 11:32:27 -04:00
Henry Schreiner 8d86d3122b tests: don't print xfails (#1865)
tests: skip warnings on xfails for now

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-06-12 10:46:24 -04:00
Eric Larson 89a5cfe272 DOC: Fix bug with link (#1870) 2024-06-11 21:24:44 +02:00
Henry Schreiner 8d5d84e0fc fix: accept current Python version if acceptable for Pyodide (#1868)
* fix: accept current Python version if acceptable for Pyodide

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-06-11 08:48:42 +02:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 4ada77dea4 [pre-commit.ci] pre-commit autoupdate (#1869)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-11 00:32:52 -04:00
Henry Schreiner 829441f4a0 fix: warning in generate schema script (#1866)
* fix: warning in generate schema script

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* docs: add minor update to update process

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Update docs/contributing.md

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-06-11 00:32:38 -04:00
Henry Schreiner a8d190a111 Bump version: v2.19.0 2024-06-10 11:28:29 -04:00
bf817c6dc8 refactor: error handling to use exceptions (#1719)
* Refactor error handling to use exceptions

cibuildwheel has up until now handled most errors by printing an error message to sys.stderr and calling sys.exit. Others were handled using Logger.error, depending on the context. We also had return codes, but these weren't explicitly defined anywhere.

This makes that convention more explicit and codified. Now to halt the program, the correct thing to do is to throw a cibuildwheel.errors.FatalError exception - that is caught in main() and printed before exiting. The existing behaviour was kept - if an error occurs within a build step (probably something to do with the build itself), the Logger.error() method is used. Outside of a build step (e.g. a misconfiguration), the behaviour is still to print 'cibuildwheel: <message>'

I also took the opportunity to add a debugging option `--debug-traceback` (and `CIBW_DEBUG_TRACEBACK`), which you can enable to see a full traceback on errors.

(I've deactivated the flake8-errmsg lint rule, as it was throwing loads of errors and these error messages aren't generally seen in a traceback context)

* add noqa rule

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>

* Return to flake8-errmsg conformance

* Code review suggestions

* Subclass Exception rather than SystemExit

* apply error handling to new code and fix merge issues

* Apply review suggestion

* fix: merge issue

* Update cibuildwheel/errors.py

---------

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2024-06-10 10:36:20 -04:00
Henry Schreiner 384c8d5c82 feat: build[uv] (#1856) 2024-06-09 15:45:31 -04:00
Matthieu Darbois c37e5a2d13 fix: util.move_file shall not use log.notice (#1862)
Using  `log.notice` result in many unwanted annotations, at least in GitHub Actions.
If we want to print some information about a specific file being moved, it shall be done by the caller - or adding an option to the function - and use `print`.
2024-06-09 19:03:12 +02:00
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>Henry SchreinerMatthieu Darbois
130fdd2548 feat: free-threaded Python for macOS, MACOSX_DEPLOYMENT_TARGET updates (#1854)
* Update dependencies

* feat: macos free-threading

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: set min target to 10.13 for 3.13 and floor it always

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: tests and corrected pypy mins

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>
2024-06-08 10:15:04 +02:00
Henry Schreiner ed120540d9 chore: bump pyodide (#1859)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-06-08 01:38:06 -04:00
MusicalNinjaDadHenry Schreinerpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
6c6e0f6ba5 fix: handle case where output_dir does not already exist on macos & windows (#1851)
* replace `with suppress(FileNotFoundError)` by `.unlink(missing_ok=True)` for macos

* also use `.unlink(missing_ok=True)` in pyodide and windows for consistency

* remove contextlib imports which are no longer required

* Apply suggestions from code review

* explicity resolve and create output location

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* use explicit str for move

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* update comments based on review feedback

* Apply suggestions from code review

* Break out functionality to move files to util.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* raise instance of IsADirectoryError with meaningful message

* Don't need a comment and a exception message

---------

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-07 11:37:20 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 877d3bf649 [pre-commit.ci] pre-commit autoupdate (#1852)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-04 08:34:28 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 21e9ef1800 [Bot] Update dependencies (#1848)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-06-04 08:32:55 +02:00
Henry Schreiner c333b131d3 fix: include VIRTUAL_ENV variable (#1842)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-05-29 08:20:02 +02:00
Henry Schreiner 4c2ed1e632 chore: remove pip <21.3 workaround (#1841)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2024-05-29 07:57:06 +02:00
Hood ChathamMatthieu DarboisHenry Schreinerpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
9f2a3cb67c feat: add Pyodide support (#1456)
* feat: add Pyodide support

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com>
Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>

tests: fix two merge issues

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

fix: include schema

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Try to fix xbuildenv path

[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Try to install pyodide-build from main branch

Try again

Try again

Update constraints file

Try again

Remove unused variable

Drop constraints

Remove --download option

Fix xbuildenv install

Try again

Try again

[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: remove pinning on pyodide

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Update for Pyodide 0.26.0a5

* Install pyodide-build from pypi

* Update docs/options.md

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>

* Unxfail things that look like they were just a version mismatch

* refactor: add constraints for pyodide

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* chore: minor cleanup

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Apply suggestions from code review

* Apply suggestion from code review

* refactor: minor touchup

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* ci: xfail the pyodide test

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* review: use a pinned version of node

* fix tests

* review: error out on Windows

* test: check node & test on macos arm64

* chore: minor cleanup

* Add reference to emscripten libc issue

* Apply suggestion from code review

* review: use a pinned pip in test virtual environment

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore: rework test virtual environment seed packages

* chore: workaround direct invocation of pytest

This allows to still test direct invocation of `pytest` on most platforms (including pyodide on Linux) but falls back to `python -m pytest` when running pyodide on macOS.

* Use release version of pyodide

* fix: tests for 0.26.0 & parallel initialization of xbuildenv

* Debug CI

* fix: test/test_build_frontend_args.py

* Revert "Debug CI"

This reverts commit 917646cffc96dbfc619c47d1c03a828f447bcdb7.

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Matthieu Darbois <mayeut@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-28 08:31:36 -04:00
pre-commit-ci[bot]andpre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> 687406f664 [pre-commit.ci] pre-commit autoupdate (#1840)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.5)
- [github.com/codespell-project/codespell: v2.2.6 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.2.6...v2.3.0)
- [github.com/python-jsonschema/check-jsonschema: 0.28.3 → 0.28.4](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.3...0.28.4)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-27 22:34:22 -04:00
Matthieu Darbois 2e6b11e10c chore(ci): split emulation tests per architecture (#1839) 2024-05-27 22:34:10 -04:00
Matthieu Darbois b713086763 chore: better determinism for the test virtual environment (#1838)
* chore: better determinism for the test virtual environment

* review: apply suggestions
2024-05-27 23:04:25 +02:00
cibuildwheel-bot[bot]andcibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com> 8f574d107b [Bot] Update dependencies (#1836)
Update dependencies

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
2024-05-27 12:28:13 -04:00
Matthieu Darbois 97da90432e chore: rework single python tests (#1835)
* chore: rework single python tests

* chore(tests): limit number of builds in test_abi3

* chore(tests): limit number of builds in test_before_all

* apply review suggestion

use `os.path.samefile` rather than comparing `os.stat` directly.
2024-05-26 12:41:20 +02:00
Matthieu Darbois 67ee9dda92 chore(tests): do not use hard-coded paths in tests (#1834)
* chore(tests): do not use hard-coded paths in tests

* review suggestion

use `os.path.samefile` rather than comparing `os.stat` directly.
2024-05-26 12:40:11 +02:00
107 changed files with 3308 additions and 1272 deletions
+7 -6
View File
@@ -1,9 +1,10 @@
version: 2
jobs:
osx-python3.9:
osx-python3.12:
macos:
xcode: 12.5.1
xcode: 15.4.0
resource_class: macos.m1.medium.gen1
environment:
PYTHON: python3
steps:
@@ -17,9 +18,9 @@ jobs:
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
linux-python3.9:
linux-python3.12:
docker:
- image: circleci/python:3.9
- image: cimg/python:3.12
environment:
PYTHON: python3
# Temporarily restrict the tests that are run on CircleCI to prevent
@@ -61,6 +62,6 @@ workflows:
version: 2
all-tests:
jobs:
- osx-python3.9
- linux-python3.9
- osx-python3.12
- linux-python3.12
- linux-aarch64
+7 -3
View File
@@ -2,10 +2,14 @@
set -o errexit
set -o xtrace
if [ "$(uname -s)" == "Darwin" ]; then
sudo softwareupdate --install-rosetta --agree-to-license
else
docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
fi
$PYTHON --version
$PYTHON -m pip --version
$PYTHON -m virtualenv --version
$PYTHON -m virtualenv --no-setuptools --no-wheel -p "$PYTHON" venv
$PYTHON -m venv venv
venv/bin/python -m pip install -U pip
venv/bin/python -m pip install -e ".[dev]"
venv/bin/python -m pip freeze
+2
View File
@@ -17,6 +17,7 @@ linux_x86_task:
memory: 8G
install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
- apt install -y python3-venv python-is-python3
<<: *RUN_TESTS
@@ -30,6 +31,7 @@ linux_aarch64_task:
memory: 4G
install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
- apt install -y python3-venv python-is-python3
<<: *RUN_TESTS
+9 -1
View File
@@ -28,9 +28,12 @@ jobs:
needs: [dist]
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
environment: pypi
environment:
name: pypi
url: https://pypi.org/p/cibuildwheel
permissions:
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@v4
@@ -39,4 +42,9 @@ jobs:
path: dist
merge-multiple: true
- name: Generate artifact attestation for sdist and wheel
uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
with:
subject-path: "dist/cibuildwheel-*"
- uses: pypa/gh-action-pypi-publish@release/v1
+77 -25
View File
@@ -22,16 +22,17 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
id: python
with:
python-version: "3.x"
- uses: pre-commit/action@v3.0.1
- name: Check manifest
run: pipx run nox -s check_manifest
run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s check_manifest
- name: PyLint checks
run: pipx run nox -s pylint -- --output-format=github
run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s pylint -- --output-format=github
test:
name: Test cibuildwheel on ${{ matrix.os }} (${{ matrix.python_version }})
name: Test on ${{ matrix.os }} (${{ matrix.python_version }})
needs: lint
runs-on: ${{ matrix.os }}
strategy:
@@ -49,16 +50,7 @@ jobs:
with:
python-version: ${{ matrix.python_version }}
# Install podman on this CI instance for podman tests on linux
# Snippet from: https://github.com/redhat-actions/podman-login/blob/main/.github/workflows/example.yml
- name: Install latest podman
if: runner.os == 'Linux'
run: |
. /etc/os-release
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -sSfL "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key" | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install podman
- uses: yezz123/setup-uv@v4
# free some space to prevent reaching GHA disk space limits
- name: Clean docker images
@@ -67,9 +59,14 @@ jobs:
docker system prune -a -f
df -h
# for oci_container unit tests
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
- name: Install dependencies
run: |
python -m pip install ".[test]"
uv pip install --system ".[test]"
- name: Generate a sample project
run: |
@@ -82,6 +79,9 @@ jobs:
output-dir: wheelhouse
env:
CIBW_ARCHS_MACOS: x86_64 universal2 arm64
CIBW_BUILD_FRONTEND: 'build[uv]'
CIBW_FREE_THREADED_SUPPORT: 1
CIBW_PRERELEASE_PYTHONS: 1
- name: Run a sample build (GitHub Action, only)
uses: ./
@@ -125,26 +125,78 @@ jobs:
run: |
python ./bin/run_tests.py --run-podman
test-emulated:
name: Test emulated cibuildwheel using qemu
emulated-archs:
name: Get qemu emulated architectures
needs: lint
runs-on: ubuntu-latest
timeout-minutes: 180
outputs:
archs: ${{ steps.archs.outputs.archs }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install dependencies
run: python -m pip install ".[test]"
- name: Get qemu emulated architectures
id: archs
run: |
OUTPUT=$(python -c "from json import dumps; from test.utils import EMULATED_ARCHS; print(dumps(EMULATED_ARCHS))")
echo "${OUTPUT}"
echo "archs=${OUTPUT}" >> "$GITHUB_OUTPUT"
test-emulated:
name: Test Linux ${{ matrix.arch }} using qemu
needs: emulated-archs
runs-on: ubuntu-latest
timeout-minutes: 180
strategy:
matrix:
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install dependencies
run: python -m pip install ".[test,uv]"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Run the emulation tests
run: pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py
test-pyodide:
name: Test cibuildwheel building pyodide wheels
needs: lint
runs-on: ubuntu-24.04
timeout-minutes: 180
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
name: Install Python 3.12
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install ".[test]"
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Run the emulation tests
- name: Generate a sample project
run: |
pytest --run-emulation test/test_emulation.py
python -m test.test_projects test.test_0_basic.basic_project sample_proj
- name: Run a sample build (GitHub Action)
uses: ./
with:
package-dir: sample_proj
output-dir: wheelhouse
env:
CIBW_PLATFORM: pyodide
- name: Run tests with 'CIBW_PLATFORM' set to 'pyodide'
run: |
python ./bin/run_tests.py
env:
CIBW_PLATFORM: pyodide
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
- name: Create Pull Request
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
commit-message: Update dependencies
title: '[Bot] Update dependencies'
+14 -3
View File
@@ -1,5 +1,5 @@
linux:
image: python:3.8
image: python:3.12
services:
- name: docker:dind
entrypoint: ["env", "-u", "DOCKER_HOST"]
@@ -15,6 +15,7 @@ linux:
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
script:
- curl -sSL https://get.docker.com/ | sh
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
- python -m pip install -e ".[dev]" pytest-custom-exit-code
- python ./bin/run_tests.py
@@ -23,9 +24,19 @@ windows:
variables:
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
before_script:
- choco install python -y --version 3.8.6
- choco install python -y --version 3.12.4
script:
- py -m pip install -e ".[dev]" pytest-custom-exit-code
- py bin\run_tests.py
tags:
- windows
- saas-windows-medium-amd64
macos:
image: macos-14-xcode-15
variables:
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
script:
- python3 -m pip install -e ".[dev]" pytest-custom-exit-code
- python3 ./bin/run_tests.py
tags:
- saas-macos-medium-m1
+6 -5
View File
@@ -14,14 +14,14 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
rev: v0.6.4
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
rev: v1.11.2
hooks:
- id: mypy
name: mypy 3.8 on cibuildwheel/
@@ -40,6 +40,7 @@ repos:
- types-jinja2
- types-pyyaml
- types-requests
- uv
- validate-pyproject
- id: mypy
name: mypy 3.12
@@ -68,15 +69,15 @@ repos:
files: ^docs/changelog.md$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell
args: ["-L", "sur", "-w"]
args: ["-L", "sur,assertin", "-w"]
exclude: ^docs/working-examples\.md$ # Autogenerated
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.3
rev: 0.29.2
hooks:
- id: check-dependabot
- id: check-github-actions
+14 -2
View File
@@ -20,6 +20,14 @@ jobs:
group: edge
virt: vm
env: PYTHON=python
# docker is outdated in the arm64-graviton2 vm focal image (19.x)
# we need to upgrade to get >= 24.0
addons:
apt:
sources:
- sourceline: 'deb https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable'
packages:
- docker-ce docker-ce-cli containerd.io
- name: Linux | ppc64le | Python 3.9
python: 3.9
@@ -44,11 +52,13 @@ jobs:
python: 3.9
services: docker
arch: s390x
allow_failure: True
env: PYTHON=python
install:
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all; fi
- $PYTHON -m pip install -U pip
- $PYTHON -m pip install -e ".[dev]" pytest-custom-exit-code
- $PYTHON -m pip install -e ".[test]" pytest-custom-exit-code
script: |
# travis_wait disable the output while waiting
@@ -56,5 +66,7 @@ script: |
(while true; do echo "travis_keep_alive"; sleep 300; done) &
SPINNER_PID=$!
disown
$PYTHON ./bin/run_tests.py --num-processes 2
result=0
$PYTHON ./bin/run_tests.py --num-processes 2 || result=1
kill -9 ${SPINNER_PID}
test ${result} -eq 0
+5 -5
View File
@@ -1,10 +1,10 @@
This is a summary of the host Python versions and platforms covered by the different CI platforms:
| | 3.8 | 3.9 | 3.10 | 3.12 |
|---------|--------------------------------------------------------|-----------------------|-----------|----------------|
| Linux | AppVeyor¹ / Azure Pipelines / GitLab¹ / GitHub Actions | CircleCI¹ / Travis CI | Cirrus CI | GitHub Actions |
| macOS | AppVeyor¹ / Azure Pipelines | CircleCI¹ / Travis CI | Cirrus CI | GitHub Actions |
| Windows | AppVeyor¹ / Azure Pipelines / GitLab¹ | Travis CI | Cirrus CI | GitHub Actions |
| | 3.8 | 3.9 | 3.10 | 3.11 | 3.12 |
|---------|----------------------------------|-----------|-----------|---------|--------------------------------------------------|
| Linux | Azure Pipelines / GitHub Actions | Travis CI | Cirrus CI | | AppVeyor¹ / CircleCI¹ / GitHub Actions / GitLab¹ |
| macOS | Azure Pipelines | | Cirrus CI | GitLab¹ | AppVeyor¹ /CircleCI¹ / GitHub Actions |
| Windows | Azure Pipelines | Travis CI | Cirrus CI | | AppVeyor¹ / GitHub Actions / GitLab¹ |
> ¹ Runs a reduced set of tests to reduce CI load
+72 -64
View File
@@ -22,24 +22,25 @@ Python wheels are great. Building them across **Mac, Linux, Windows**, on **mult
What does it do?
----------------
| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux<br/>musllinux x86_64 | manylinux<br/>musllinux i686 | manylinux<br/>musllinux aarch64 | manylinux<br/>musllinux ppc64le | manylinux<br/>musllinux s390x |
|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|
| CPython 3.6 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.7 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ |
| PyPy 3.7 v7.3 | ✅ | N/A | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A |
| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A |
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A |
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A |
| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux<br/>musllinux x86_64 | manylinux<br/>musllinux i686 | manylinux<br/>musllinux aarch64 | manylinux<br/>musllinux ppc64le | manylinux<br/>musllinux s390x | Pyodide |
|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|-----|
| CPython 3.6 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.7 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁴ |
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| PyPy 3.7 v7.3 | ✅ | N/A | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
<sup>¹ PyPy is only supported for manylinux wheels.</sup><br>
<sup>² Windows arm64 support is experimental.</sup><br>
<sup>³ CPython 3.13 is available using the [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.pypa.io/en/stable/options/#prerelease-pythons) option.</sup><br>
<sup>³ CPython 3.13 is built by default using Python RCs, starting with cibuildwheel 2.20. Free-threaded mode will still require opt-in using [`CIBW_FREE_THREADED_SUPPORT`](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support).</sup><br>
<sup>⁴ Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.</sup><br>
- Builds manylinux, musllinux, macOS 10.9+, and Windows wheels for CPython and PyPy
- Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, and Cirrus CI
@@ -60,7 +61,7 @@ Usage
| Travis CI | ✅ | | ✅ | ✅ | | |
| AppVeyor | ✅ | ✅ | ✅ | | ✅ | ✅² |
| CircleCI | ✅ | ✅ | | ✅ | ✅ | |
| Gitlab CI | ✅ | | ✅ | ✅¹ | | |
| Gitlab CI | ✅ | | ✅ | ✅¹ | | |
| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | |
<sup>¹ [Requires emulation](https://cibuildwheel.pypa.io/en/stable/faq/#emulation), distributed separately. Other services may also support Linux ARM through emulation or third-party build hosts, but these are not tested in our CI.</sup><br>
@@ -93,7 +94,7 @@ jobs:
- uses: actions/setup-python@v5
- name: Install cibuildwheel
run: python -m pip install cibuildwheel==2.18.1
run: python -m pip install cibuildwheel==2.21.0
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
@@ -160,23 +161,23 @@ Here are some repos that use cibuildwheel.
| [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The fundamental package for scientific computing with Python. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system |
| [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [Prophet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth. |
| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an in-process SQL OLAP Database Management System |
| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS |
[scikit-learn]: https://github.com/scikit-learn/scikit-learn
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NumPy]: https://github.com/numpy/numpy
[duckdb]: https://github.com/duckdb/duckdb
[Tornado]: https://github.com/tornadoweb/tornado
[Matplotlib]: https://github.com/matplotlib/matplotlib
[NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib
[Prophet]: https://github.com/facebook/prophet
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[duckdb]: https://github.com/duckdb/duckdb
[Kivy]: https://github.com/kivy/kivy
[appveyor icon]: docs/data/readme_icons/appveyor.svg
@@ -210,65 +211,72 @@ Changelog
<!-- this section was generated by bin/update_readme_changelog.py -- do not edit manually -->
### v2.18.1
### v2.21.0
- 🌟 Add free-threaded Linux and Windows builds for 3.13. New identifiers `cp313t-*`, new option `CIBW_FREE_THREADED_SUPPORT`/`tool.cibuildwheel.free-threaded-support` required to opt-in. [See the docs](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support) for more information. (#1831)
- ✨ The `container-engine` is now a build (non-global) option. (#1792)
- 🛠 The build backend for cibuildwheel is now hatchling. (#1297)
- 🛠 Significant improvements and modernization to our noxfile. (#1823)
- 🛠 Use pylint's new GitHub Actions reporter instead of a custom matcher. (#1823)
- 🛠 Unpin virtualenv updates for Python 3.7+ (#1830)
- 🐛 Fix running linux tests from Windows or macOS ARM. (#1788)
- 📚 Fix our documentation build. (#1821)
_13 September 2024_
- ⚠️ Update CPython 3.12 to 3.12.6, which changes the macOS minimum deployment target on CPython 3.12 from macOS 10.9 to macOS 10.13 (#1998)
- 🛠 Changes the behaviour when inheriting `config-settings` in TOML overrides - rather than extending each key, which is rarely useful, individual keys will override previously set values. (#1803)
- 🛠 Update CPython 3.13 to 3.13.0rc2 (#1998)
- ✨ Adds support for multiarch OCI images (#1961)
- 🐛 Fixes some bugs building Linux wheels on macOS. (#1961)
- ⚠️ Changes the minimum version of Docker/Podman to Docker API version 1.43, Podman API version 3. The only mainstream runner this should affect is Travis Graviton2 runners - if so you can [upgrade your version of Docker](https://github.com/pypa/cibuildwheel/pull/1961#issuecomment-2304060019). (#1961)
### v2.18.0
### v2.20.0
_12 May 2024_
_4 August 2024_
- ✨ Adds CPython 3.13 support, under the prerelease flag [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.pypa.io/en/stable/options/#prerelease-pythons). This version of cibuildwheel uses 3.13.0b1. Free-threading mode is not available yet (#1657), waiting on official binaries (planned for beta 2) and pip support.
_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.13 will be available in cibuildwheel without the flag._ (#1815)
- ✨ Musllinux now defaults to `musllinux_1_2`. You can set the older `musllinux_1_1` via config if needed. (#1817)
- 🛠 No longer pre-seed setuptools/wheel in virtual environments (#1819)
- 🛠 Respect the constraints file when building with pip, matching build (#1818)
- 🛠 Use uv to compile our pinned dependencies, 10x faster and doesn't require special setup (#1778)
- 🐛 Fix an issue with the schema (#1788)
- 📚 Document the new delocate error checking macOS versions (#1766)
- 📚 Document Rust builds (#1816)
- 📚 Speed up our readthedocs builds with uv, 26 seconds -> 6 seconds to install dependencies (#1816)
- 🌟 CPython 3.13 wheels are now built by default - without the `CIBW_PRERELEASE_PYTHONS` flag. It's time to build and upload these wheels to PyPI! This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release. Free-threading is still behind a flag/config option. (#1950)
- ✨ Provide a `CIBW_ALLOW_EMPTY` environment variable as an alternative to the command line flag. (#1937)
- 🐛 Don't use uv on PyPy3.8 on Windows, it stopped working starting in 0.2.25. Note that PyPy 3.8 is EoL. (#1868)
- 🛠 Set the `VSCMD_ARG_TGT_ARCH` variable based on target arch. (#1876)
- 🛠 Undo cleaner output on pytest 8-8.2 now that 8.3 is out. (#1943)
- 📚 Update examples to use Python 3.12 on host (cibuildwheel will require Python 3.11+ on the host machine starting in October 2024) (#1919)
### v2.17.0
### v2.19.2
_11 March 2024_
_2 July 2024_
- 🌟 Adds the ability to inherit configuration in TOML overrides. This makes certain configurations much simpler. If you're overriding an option like `before-build` or `environment`, and you just want to add an extra command or environment variable, you can just append (or prepend) to the previous config. See [the docs](https://cibuildwheel.pypa.io/en/stable/options/#inherit) for more information. (#1730)
- 🌟 Adds official support for native `arm64` macOS GitHub runners. To use them, just specify `macos-14` as an `os` of your job in your workflow file. You can also keep `macos-13` in your build matrix to build `x86_64`. Check out the new [GitHub Actions example config](https://cibuildwheel.pypa.io/en/stable/setup/#github-actions).
- ✨ You no longer need to specify `--platform` to run cibuildwheel locally! Instead it will detect your platform automatically. This was a safety feature, no longer necessary. (#1727)
- 🛠 Removed setuptools and wheel pinned versions. This only affects old-style projects without a `pyproject.toml`, projects with `pyproject.toml` are already getting fresh versions of their `build-system.requires` installed into an isolated environment. (#1725)
- 🛠 Improve how the GitHub Action passes arguments (#1757)
- 🛠 Remove a system-wide install of pipx in the GitHub Action (#1745)
- 🐛 No longer will cibuildwheel override the `PIP_CONSTRAINT` environment variable when using the `build` frontend. Instead it will be extended. (#1675)
- 🐛 Fix a bug where building and testing both `x86_86` and `arm64` wheels on the same runner caused the wrong architectures in the test environment (#1750)
- 🐛 Fix a bug that prevented testing a CPython 3.8 wheel targeting macOS 11+ on `x86_64` (#1768)
- 📚 Moved the docs onto the official PyPA domain - they're now available at https://cibuildwheel.pypa.io . (#1775)
- 📚 Docs and examples improvements (#1762, #1734)
- 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917)
- 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889)
- 🛠 Provide attestations for releases at <https://github.com/pypa/cibuildwheel/attestations>. (#1916)
- 🛠 Provide CPython 3.13.0b3. (#1913)
- 🛠 Remove some workarounds now that pip 21.1 is available. (#1891, #1892)
- 📚 Remove nosetest from our docs. (#1821)
- 📚 Document the macOS ARM workaround for 3.8 on GHA. (#1871)
- 📚 GitLab CI + macOS is now a supported platform with an example. (#1911)
### v2.16.5
### v2.19.1
_30 January 2024_
_13 June 2024_
- 🐛 Fix an incompatibility with the GitHub Action and new GitHub Runner images for Windows that bundle Powershell 7.3+ (#1741)
- 🛠 Preliminary support for new `macos-14` arm64 runners (#1743)
- 🐛 Don't require setup-python on GHA for Pyodide (#1868)
- 🐛 Specify full python path for uv (fixes issue in 0.2.10 & 0.2.11) (#1881)
- 🛠 Update for pip 24.1b2 on CPython 3.13. (#1879)
- 🛠 Fix a warning in our schema generation script. (#1866)
- 🛠 Cleaner output on pytest 8-8.2. (#1865)
### v2.16.4
_28 January 2024_
### v2.19.0
- 🛠 Update manylinux pins to upgrade from a problematic PyPy version. (#1737)
_10 June 2024_
See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more info on new features!
- 🌟 Add Pyodide platform. Set with `--platform pyodide` or `CIBW_PLATFORM: pyodide` on Linux with a host Python 3.12 to build WebAssembly wheels. Not accepted on PyPI currently, but usable directly in a website using Pyodide, for live docs, etc. (#1456, #1859)
- 🌟 Add `build[uv]` backend, which will take a pre-existing uv install (or install `cibuildwheel[uv]`) and use `uv` for all environment setup and installs on Python 3.8+. This is significantly faster in most cases. (#1856)
- ✨ Add free-threaded macOS builds and update CPython to 3.13.0b2. (#1854)
- 🐛 Issue copying a wheel to a non-existent output dir fixed. (#1851, #1862)
- 🐛 Better determinism for the test environment seeding. (#1835)
- 🛠 `VIRTUAL_ENV` variable now set. (#1842)
- 🛠 Remove a pip<21.3 workaround. (#1842)
- 🛠 Error handling was refactored to use exceptions. (#1719)
- 🛠 Hardcoded paths in tests avoided. (#1834)
- 🛠 Single Python tests made more generic. (#1835)
- 🛠 Sped up our ci by splitting up emulation tests. (#1839)
<!-- END bin/update_readme_changelog.py -->
+2 -2
View File
@@ -24,11 +24,11 @@ branding:
runs:
using: composite
steps:
# Set up a non-EOL, cibuildwheel & pipx supported Python version
# Set up the version of Python that supports pyodide
- uses: actions/setup-python@v5
id: python
with:
python-version: "3.8 - 3.12"
python-version: "3.12"
update-environment: false
- id: cibw
+14 -11
View File
@@ -1,26 +1,29 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
APPVEYOR_JOB_NAME: "python38-x64-ubuntu"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
APPVEYOR_JOB_NAME: "python38-x64-vs2015"
- APPVEYOR_BUILD_WORKER_IMAGE: macos
APPVEYOR_JOB_NAME: "python38-x64-macos"
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204
APPVEYOR_JOB_NAME: "python312-x64-ubuntu"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
APPVEYOR_JOB_NAME: "python312-x64-vs2022"
- APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma
APPVEYOR_JOB_NAME: "python312-x64-macos"
stack: python 3.8
stack: python 3.12
build: off
init:
- cmd: set PATH=C:\Python38;C:\Python38\Scripts;%PATH%
- ps: |
$BRANCH = if ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) { $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH } else { $env:APPVEYOR_REPO_BRANCH }
if (-not ($BRANCH -eq 'main' -or $BRANCH.ToLower().StartsWith('appveyor-'))) {
$env:PYTEST_ADDOPTS='-k "unit_test or test_0_basic" --suppress-no-test-exit-code'
$env:PYTEST_ADDOPTS = '-k "unit_test or test_0_basic" --suppress-no-test-exit-code'
}
if ($IsLinux) {
docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
}
python -m pip install -U pip
install: python -m pip install -e ".[dev]" pytest-custom-exit-code
install:
- python -m pip install -U pip
- python -m pip install -e ".[dev]" pytest-custom-exit-code
# the '-u' flag is required so the output is in the correct order.
# See https://github.com/pypa/cibuildwheel/pull/24 for more info.
+2 -1
View File
@@ -13,11 +13,12 @@ jobs:
inputs:
versionSpec: '3.8'
- bash: |
docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
python -m pip install -e ".[dev]"
python ./bin/run_tests.py
- job: macos_38
pool: {vmImage: 'macOS-11'}
pool: {vmImage: 'macOS-12'}
steps:
- task: UsePythonVersion@0
inputs:
+6 -3
View File
@@ -48,19 +48,21 @@ properties:
type: string_array
build-frontend:
default: default
description: Set the tool to use to build, either "pip" (default for now) or "build"
description: Set the tool to use to build, either "pip" (default for now), "build", or "build[uv]"
oneOf:
- enum: [pip, build, default]
- enum: [pip, build, "build[uv]", default]
- type: string
pattern: '^pip; ?args:'
- type: string
pattern: '^build; ?args:'
- type: string
pattern: '^build\\[uv\\]; ?args:'
- type: object
additionalProperties: false
required: [name]
properties:
name:
enum: [pip, build]
enum: [pip, build, "build[uv]"]
args:
type: array
items:
@@ -280,6 +282,7 @@ oses = {
"linux": as_object(non_global_options),
"windows": as_object(not_linux),
"macos": as_object(not_linux),
"pyodide": as_object(not_linux),
}
oses["linux"]["properties"]["repair-wheel-command"] = {
+1 -1
View File
@@ -28,7 +28,7 @@ if __name__ == "__main__":
# unit tests
unit_test_args = [sys.executable, "-m", "pytest", "unit_test"]
if sys.platform.startswith("linux"):
if sys.platform.startswith("linux") and os.environ.get("CIBW_PLATFORM", "linux") == "linux":
# run the docker unit tests only on Linux
unit_test_args += ["--run-docker"]
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
from __future__ import annotations
import difflib
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Final
import click
import packaging.specifiers
import requests
import rich
from packaging.version import InvalidVersion, Version
from rich.logging import RichHandler
from rich.syntax import Syntax
from cibuildwheel._compat import tomllib
log = logging.getLogger("cibw")
# Looking up the dir instead of using utils.resources_dir
# since we want to write to it.
DIR: Final[Path] = Path(__file__).parent.parent.resolve()
RESOURCES_DIR: Final[Path] = DIR / "cibuildwheel/resources"
NODEJS_DIST: Final[str] = "https://nodejs.org/dist/"
NODEJS_INDEX: Final[str] = f"{NODEJS_DIST}index.json"
@dataclass(frozen=True, order=True)
class VersionTuple:
version: Version
version_string: str
def parse_nodejs_index() -> list[VersionTuple]:
versions: list[VersionTuple] = []
response = requests.get(NODEJS_INDEX)
response.raise_for_status()
versions_info = response.json()
for version_info in versions_info:
version_string = version_info.get("version", "???")
if not version_info.get("lts", False):
log.debug("Ignoring non LTS release %r", version_string)
continue
if "linux-x64" not in version_info.get("files", []):
log.warning(
"Ignoring release %r which does not include a linux-x64 binary", version_string
)
continue
try:
version = Version(version_string)
if version.is_devrelease:
log.info("Ignoring development release %r", str(version))
continue
if version.is_prerelease:
log.info("Ignoring pre-release %r", str(version))
continue
versions.append(VersionTuple(version, version_string))
except InvalidVersion:
log.warning("Ignoring release %r", version_string)
versions.sort(reverse=True)
return versions
@click.command()
@click.option("--force", is_flag=True)
@click.option(
"--level", default="INFO", type=click.Choice(["WARNING", "INFO", "DEBUG"], case_sensitive=False)
)
def update_nodejs(force: bool, level: str) -> None:
logging.basicConfig(
level="INFO",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True, markup=True)],
)
log.setLevel(level)
toml_file_path = RESOURCES_DIR / "nodejs.toml"
original_toml = toml_file_path.read_text()
with toml_file_path.open("rb") as f:
nodejs_data = tomllib.load(f)
nodejs_data.pop("url")
major_versions = [VersionTuple(Version(key), key) for key in nodejs_data]
major_versions.sort(reverse=True)
versions = parse_nodejs_index()
# update existing versions, 1 per LTS
for major_version in major_versions:
current = Version(nodejs_data[major_version.version_string])
specifier = packaging.specifiers.SpecifierSet(
specifiers=f"=={major_version.version.major}.*"
)
for version in versions:
if specifier.contains(version.version) and version.version > current:
nodejs_data[major_version.version_string] = version.version_string
break
# check for a new major LTS to insert
if versions and versions[0].version.major > major_versions[0].version.major:
major_versions.insert(
0,
VersionTuple(Version(str(versions[0].version.major)), f"v{versions[0].version.major}"),
)
nodejs_data[major_versions[0].version_string] = versions[0].version_string
versions_toml = "\n".join(
f'{major_version.version_string} = "{nodejs_data[major_version.version_string]}"'
for major_version in major_versions
)
result_toml = f'url = "{NODEJS_DIST}"\n{versions_toml}\n'
rich.print() # spacer
if original_toml == result_toml:
rich.print("[green]Check complete, nodejs version unchanged.")
return
rich.print("nodejs version updated.")
rich.print("Changes:")
rich.print()
toml_relpath = toml_file_path.relative_to(DIR).as_posix()
diff_lines = difflib.unified_diff(
original_toml.splitlines(keepends=True),
result_toml.splitlines(keepends=True),
fromfile=toml_relpath,
tofile=toml_relpath,
)
rich.print(Syntax("".join(diff_lines), "diff", theme="ansi_light"))
rich.print()
if force:
toml_file_path.write_text(result_toml)
rich.print("[green]TOML file updated.")
else:
rich.print("[yellow]File left unchanged. Use --force flag to update.")
if __name__ == "__main__":
update_nodejs()
+1 -1
View File
@@ -1,3 +1,3 @@
from __future__ import annotations
__version__ = "2.18.1"
__version__ = "2.21.0"
+75 -36
View File
@@ -1,11 +1,13 @@
from __future__ import annotations
import argparse
import dataclasses
import os
import shutil
import sys
import tarfile
import textwrap
import traceback
import typing
from collections.abc import Iterable, Sequence, Set
from pathlib import Path
@@ -15,8 +17,10 @@ from typing import Protocol
import cibuildwheel
import cibuildwheel.linux
import cibuildwheel.macos
import cibuildwheel.pyodide
import cibuildwheel.util
import cibuildwheel.windows
from cibuildwheel import errors
from cibuildwheel._compat.typing import assert_never
from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.logger import log
@@ -30,10 +34,38 @@ from cibuildwheel.util import (
chdir,
detect_ci_provider,
fix_ansi_codes_for_github_actions,
strtobool,
)
@dataclasses.dataclass
class GlobalOptions:
print_traceback_on_error: bool = True # decides what happens when errors are hit.
def main() -> None:
global_options = GlobalOptions()
try:
main_inner(global_options)
except errors.FatalError as e:
message = e.args[0]
if log.step_active:
log.step_end_with_error(message)
else:
print(f"cibuildwheel: {message}", file=sys.stderr)
if global_options.print_traceback_on_error:
traceback.print_exc(file=sys.stderr)
sys.exit(e.return_code)
def main_inner(global_options: GlobalOptions) -> None:
"""
`main_inner` is the same as `main`, but it raises FatalError exceptions
rather than exiting directly.
"""
parser = argparse.ArgumentParser(
description="Build wheels for all the platforms.",
epilog="""
@@ -45,7 +77,7 @@ def main() -> None:
parser.add_argument(
"--platform",
choices=["auto", "linux", "macos", "windows"],
choices=["auto", "linux", "macos", "windows", "pyodide"],
default=None,
help="""
Platform to build for. Use this option to override the
@@ -131,8 +163,17 @@ def main() -> None:
help="Enable pre-release Python versions if available.",
)
parser.add_argument(
"--debug-traceback",
action="store_true",
default=strtobool(os.environ.get("CIBW_DEBUG_TRACEBACK", "0")),
help="Print a full traceback for all errors",
)
args = CommandLineArguments(**vars(parser.parse_args()))
global_options.print_traceback_on_error = args.debug_traceback
args.package_dir = args.package_dir.resolve()
# This are always relative to the base directory, even in SDist builds
@@ -176,11 +217,10 @@ def _compute_platform_only(only: str) -> PlatformName:
return "macos"
if "win_" in only or "win32" in only:
return "windows"
print(
f"Invalid --only='{only}', must be a build selector with a known platform",
file=sys.stderr,
)
sys.exit(2)
if "pyodide_" in only:
return "pyodide"
msg = f"Invalid --only='{only}', must be a build selector with a known platform"
raise errors.ConfigurationError(msg)
def _compute_platform_auto() -> PlatformName:
@@ -191,34 +231,27 @@ def _compute_platform_auto() -> PlatformName:
elif sys.platform == "win32":
return "windows"
else:
print(
msg = (
'cibuildwheel: Unable to detect platform from "sys.platform". cibuildwheel doesn\'t '
"support building wheels for this platform. You might be able to build for a different "
"platform using the --platform argument. Check --help output for more information.",
file=sys.stderr,
"platform using the --platform argument. Check --help output for more information."
)
sys.exit(2)
raise errors.ConfigurationError(msg)
def _compute_platform(args: CommandLineArguments) -> PlatformName:
platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto")
platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "") or "auto"
if args.only and args.platform is not None:
print(
"--platform cannot be specified with --only, it is computed from --only",
file=sys.stderr,
)
sys.exit(2)
msg = "--platform cannot be specified with --only, it is computed from --only"
raise errors.ConfigurationError(msg)
if args.only and args.archs is not None:
print(
"--arch cannot be specified with --only, it is computed from --only",
file=sys.stderr,
)
sys.exit(2)
msg = "--arch cannot be specified with --only, it is computed from --only"
raise errors.ConfigurationError(msg)
if platform_option_value not in PLATFORMS | {"auto"}:
print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr)
sys.exit(2)
msg = f"Unsupported platform: {platform_option_value}"
raise errors.ConfigurationError(msg)
if args.only:
return _compute_platform_only(args.only)
@@ -246,11 +279,18 @@ def get_platform_module(platform: PlatformName) -> PlatformModule:
return cibuildwheel.windows
if platform == "macos":
return cibuildwheel.macos
if platform == "pyodide":
return cibuildwheel.pyodide
assert_never(platform)
def build_in_directory(args: CommandLineArguments) -> None:
platform: PlatformName = _compute_platform(args)
if platform == "pyodide" and sys.platform == "win32":
msg = "cibuildwheel: Building for pyodide is not supported on Windows"
print(msg, file=sys.stderr)
sys.exit(2)
options = compute_options(platform=platform, command_line_arguments=args, env=os.environ)
package_dir = options.globals.package_dir
@@ -258,9 +298,8 @@ def build_in_directory(args: CommandLineArguments) -> None:
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ", ".join(sorted(package_files, reverse=True))
msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package"
print(msg, file=sys.stderr)
sys.exit(2)
msg = f"Could not find any of {{{names}}} at root of package"
raise errors.ConfigurationError(msg)
platform_module = get_platform_module(platform)
identifiers = get_build_identifiers(
@@ -280,7 +319,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
# 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) # type: ignore[assignment]
sys.stdout = Unbuffered(sys.stdout)
# create the cache dir before it gets printed & builds performed
CIBW_CACHE_PATH.mkdir(parents=True, exist_ok=True)
@@ -291,16 +330,14 @@ def build_in_directory(args: CommandLineArguments) -> None:
options.check_for_invalid_configuration(identifiers)
allowed_architectures_check(platform, options.globals.architectures)
except ValueError as err:
print("cibuildwheel:", *err.args, file=sys.stderr)
sys.exit(4)
raise errors.DeprecationError(*err.args) from err
if not identifiers:
print(
f"cibuildwheel: No build identifiers selected: {options.globals.build_selector}",
file=sys.stderr,
)
if not args.allow_empty:
sys.exit(3)
message = f"No build identifiers selected: {options.globals.build_selector}"
if options.globals.allow_empty:
print(f"cibuildwheel: {message}", file=sys.stderr)
else:
raise errors.NothingToDoError(message)
output_dir = options.globals.output_dir
@@ -355,7 +392,9 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str])
def get_build_identifiers(
platform_module: PlatformModule, build_selector: BuildSelector, architectures: Set[Architecture]
platform_module: PlatformModule,
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[str]:
python_configurations = platform_module.get_python_configurations(build_selector, architectures)
return [config.identifier for config in python_configurations]
+21 -5
View File
@@ -15,6 +15,7 @@ PRETTY_NAMES: Final[dict[PlatformName, str]] = {
"linux": "Linux",
"macos": "macOS",
"windows": "Windows",
"pyodide": "Pyodide",
}
ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [
@@ -46,6 +47,9 @@ class Architecture(Enum):
AMD64 = "AMD64"
ARM64 = "ARM64"
# WebAssembly
wasm32 = "wasm32"
# Allow this to be sorted
def __lt__(self, other: Architecture) -> bool:
return self.value < other.value
@@ -60,7 +64,9 @@ class Architecture(Enum):
if arch_str == "auto":
result |= Architecture.auto_archs(platform=platform)
elif arch_str == "native":
result.add(Architecture(platform_module.machine()))
native_arch = Architecture.native_arch(platform=platform)
if native_arch:
result.add(native_arch)
elif arch_str == "all":
result |= Architecture.all_archs(platform=platform)
elif arch_str == "auto64":
@@ -72,8 +78,9 @@ class Architecture(Enum):
return result
@staticmethod
def auto_archs(platform: PlatformName) -> set[Architecture]:
native_machine = platform_module.machine()
def native_arch(platform: PlatformName) -> Architecture | None:
if platform == "pyodide":
return Architecture.wasm32
# Cross-platform support. Used for --print-build-identifiers or docker builds.
host_platform: PlatformName = (
@@ -82,6 +89,7 @@ class Architecture(Enum):
else ("macos" if sys.platform.startswith("darwin") else "linux")
)
native_machine = platform_module.machine()
native_architecture = Architecture(native_machine)
# we might need to rename the native arch to the machine we're running
@@ -93,11 +101,18 @@ class Architecture(Enum):
if synonym is None:
# can't build anything on this platform
return set()
return None
native_architecture = Architecture(synonym)
result = {native_architecture}
return native_architecture
@staticmethod
def auto_archs(platform: PlatformName) -> set[Architecture]:
native_arch = Architecture.native_arch(platform)
if native_arch is None:
return set() # can't build anything on this platform
result = {native_arch}
if platform == "linux" and Architecture.x86_64 in result:
# x86_64 machines can run i686 containers
@@ -120,6 +135,7 @@ class Architecture(Enum):
},
"macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2},
"windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64},
"pyodide": {Architecture.wasm32},
}
return all_archs_map[platform]
+64
View File
@@ -0,0 +1,64 @@
"""
Errors that can cause the build to fail. Each subclass of FatalError has
a different return code, by defining them all here, we can ensure that they're
semantically clear and unique.
"""
import textwrap
class FatalError(BaseException):
"""
Raising an error of this type will cause the message to be printed to
stderr and the process to be terminated. Within cibuildwheel, raising this
exception produces a better error message, and optional traceback.
"""
return_code: int = 1
class ConfigurationError(FatalError):
return_code = 2
class NothingToDoError(FatalError):
return_code = 3
class DeprecationError(FatalError):
return_code = 4
class NonPlatformWheelError(FatalError):
def __init__(self) -> None:
message = textwrap.dedent(
"""
Build failed because a pure Python wheel was generated.
If you intend to build a pure-Python wheel, you don't need cibuildwheel - use
`pip wheel -w DEST_DIR .` instead.
If you expected a platform wheel, check your project configuration, or run
cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
"""
)
super().__init__(message)
self.return_code = 5
class AlreadyBuiltWheelError(FatalError):
def __init__(self, wheel_name: str) -> None:
message = textwrap.dedent(
f"""
Build failed because a wheel named {wheel_name} was already generated in the current run.
If you expected another wheel to be generated, check your project configuration, or run
cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
"""
)
super().__init__(message)
self.return_code = 6
class OCIEngineTooOldError(FatalError):
return_code = 7
+65 -53
View File
@@ -8,17 +8,18 @@ from dataclasses import dataclass
from pathlib import Path, PurePath, PurePosixPath
from typing import OrderedDict, Tuple
from packaging.version import Version
from . import errors
from ._compat.typing import assert_never
from .architecture import Architecture
from .logger import log
from .oci_container import OCIContainer, OCIContainerEngineConfig
from .oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from .options import BuildOptions, Options
from .typing import PathOrStr
from .util import (
AlreadyBuiltWheelError,
BuildFrontendConfig,
BuildSelector,
NonPlatformWheelError,
find_compatible_wheel,
get_build_verbosity_extra_flags,
prepare_command,
@@ -28,6 +29,14 @@ from .util import (
unwrap,
)
ARCHITECTURE_OCI_PLATFORM_MAP = {
Architecture.x86_64: OCIPlatform.AMD64,
Architecture.i686: OCIPlatform.i386,
Architecture.aarch64: OCIPlatform.ARM64,
Architecture.ppc64le: OCIPlatform.PPC64LE,
Architecture.s390x: OCIPlatform.S390X,
}
@dataclass(frozen=True)
class PythonConfiguration:
@@ -145,11 +154,7 @@ def check_all_python_exist(
exist = False
if not exist:
message = "\n".join(messages)
print(
f"cibuildwheel:\n{message}",
file=sys.stderr,
)
sys.exit(1)
raise errors.FatalError(message)
def build_in_container(
@@ -194,9 +199,13 @@ def build_in_container(
log.build_start(config.identifier)
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = build_frontend.name == "build[uv]" and Version(config.version) >= Version("3.8")
pip = ["uv", "pip"] if use_uv else ["pip"]
dependency_constraint_flags: list[PathOrStr] = []
log.step("Setting up build environment...")
if build_options.dependency_constraints:
constraints_file = build_options.dependency_constraints.get_for_python_version(
config.version
@@ -206,8 +215,6 @@ def build_in_container(
container.copy_into(constraints_file, container_constraints_file)
dependency_constraint_flags = ["-c", container_constraints_file]
log.step("Setting up build environment...")
env = container.get_environment()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
env["PIP_ROOT_USER_ACTION"] = "ignore"
@@ -221,19 +228,19 @@ def build_in_container(
# check config python is still on PATH
which_python = container.call(["which", "python"], env=env, capture_output=True).strip()
if PurePosixPath(which_python) != python_bin / "python":
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg)
which_pip = container.call(["which", "pip"], env=env, capture_output=True).strip()
if PurePosixPath(which_pip) != python_bin / "pip":
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
if use_uv:
which_uv = container.call(["which", "uv"], env=env, capture_output=True).strip()
if not which_uv:
msg = "uv not found on PATH. You must use a supported manylinux or musllinux environment with uv."
raise errors.FatalError(msg)
else:
which_pip = container.call(["which", "pip"], env=env, capture_output=True).strip()
if PurePosixPath(which_pip) != python_bin / "pip":
msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
@@ -277,10 +284,12 @@ def build_in_container(
],
env=env,
)
elif build_frontend.name == "build":
elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags += ["--installer=uv"]
container.call(
[
"python",
@@ -303,7 +312,7 @@ def build_in_container(
container.call(["mkdir", "-p", repaired_wheel_dir])
if built_wheel.name.endswith("none-any.whl"):
raise NonPlatformWheelError()
raise errors.NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
@@ -318,28 +327,36 @@ def build_in_container(
for repaired_wheel in repaired_wheels:
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise AlreadyBuiltWheelError(repaired_wheel.name)
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
if build_options.test_command and build_options.test_selector(config.identifier):
log.step("Testing wheel...")
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
container.call(["pip", "install", "virtualenv", *dependency_constraint_flags], env=env)
if not use_uv:
container.call(
["pip", "install", "virtualenv", *dependency_constraint_flags], env=env
)
testing_temp_dir = PurePosixPath(
container.call(["mktemp", "-d"], capture_output=True).strip()
)
venv_dir = testing_temp_dir / "venv"
container.call(["python", "-m", "virtualenv", "--no-download", venv_dir], env=env)
if use_uv:
container.call(["uv", "venv", venv_dir], env=env)
else:
# Use embedded dependencies from virtualenv to ensure determinism
venv_args = ["--no-periodic-update", "--pip=embed"]
# In Python<3.12, setuptools & wheel are installed as well
if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
container.call(["python", "-m", "virtualenv", *venv_args, venv_dir], env=env)
virtualenv_env = env.copy()
virtualenv_env["PATH"] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}"
# TODO remove me once virtualenv provides pip>=24.1b1
if config.version == "3.13":
container.call(["pip", "install", "pip>=24.1b1"], env=virtualenv_env)
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
if build_options.before_test:
before_test_prepared = prepare_command(
@@ -357,13 +374,13 @@ def build_in_container(
# Let's just pick the first one.
wheel_to_test = repaired_wheels[0]
container.call(
["pip", "install", str(wheel_to_test) + build_options.test_extras],
[*pip, "install", str(wheel_to_test) + build_options.test_extras],
env=virtualenv_env,
)
# Install any requirements to run the tests
if build_options.test_requires:
container.call(["pip", "install", *build_options.test_requires], env=virtualenv_env)
container.call([*pip, "install", *build_options.test_requires], env=virtualenv_env)
# Run the tests from a different directory
test_command_prepared = prepare_command(
@@ -419,31 +436,29 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
check=True,
stdout=subprocess.DEVNULL,
)
except subprocess.CalledProcessError:
print(
unwrap(
f"""
cibuildwheel: {build_step.container_engine.name} not found. An
OCI exe like Docker or Podman is required to run Linux builds.
If you're building on Travis CI, add `services: [docker]` to
your .travis.yml. If you're building on Circle CI in Linux,
add a `setup_remote_docker` step to your .circleci/config.yml.
If you're building on Cirrus CI, use `docker_builder` task.
"""
),
file=sys.stderr,
except subprocess.CalledProcessError as error:
msg = unwrap(
f"""
cibuildwheel: {build_step.container_engine.name} not found. An
OCI exe like Docker or Podman is required to run Linux builds.
If you're building on Travis CI, add `services: [docker]` to
your .travis.yml. If you're building on Circle CI in Linux,
add a `setup_remote_docker` step to your .circleci/config.yml.
If you're building on Cirrus CI, use `docker_builder` task.
"""
)
sys.exit(2)
raise errors.ConfigurationError(msg) from error
try:
ids_to_build = [x.identifier for x in build_step.platform_configs]
log.step(f"Starting container image {build_step.container_image}...")
print(f"info: This container will host the build for {', '.join(ids_to_build)}...")
architecture = Architecture(build_step.platform_tag.split("_", 1)[1])
with OCIContainer(
image=build_step.container_image,
enforce_32_bit=build_step.platform_tag.endswith("i686"),
oci_platform=ARCHITECTURE_OCI_PLATFORM_MAP[architecture],
cwd=container_project_path,
engine=build_step.container_engine,
) as container:
@@ -456,11 +471,9 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
)
except subprocess.CalledProcessError as error:
log.step_end_with_error(
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
)
troubleshoot(options, error)
sys.exit(1)
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
raise errors.FatalError(msg) from error
def _matches_prepared_command(error_cmd: Sequence[str], command_template: str) -> bool:
@@ -479,7 +492,6 @@ def troubleshoot(options: Options, error: Exception) -> None:
) # TODO allow matching of overrides too?
):
# the wheel build step or the repair step failed
print("Checking for common errors...")
so_files = list(options.globals.package_dir.glob("**/*.so"))
if so_files:
+5
View File
@@ -34,6 +34,7 @@ PLATFORM_IDENTIFIER_DESCRIPTIONS: Final[dict[str, str]] = {
"macosx_x86_64": "macOS x86_64",
"macosx_universal2": "macOS Universal 2 - x86_64 and arm64",
"macosx_arm64": "macOS arm64 - Apple Silicon",
"pyodide_wasm32": "Pyodide",
}
@@ -151,6 +152,10 @@ class Logger:
c = self.colors
print(f"{c.bright_red}Error{c.end}: {error}\n", file=sys.stderr)
@property
def step_active(self) -> bool:
return self.step_start_time is not None
def _start_fold_group(self, name: str) -> None:
self._end_fold_group()
self.active_fold_group_name = name
+174 -78
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import contextlib
import functools
import os
import platform
@@ -15,7 +14,9 @@ from pathlib import Path
from typing import Literal, Tuple
from filelock import FileLock
from packaging.version import Version
from . import errors
from ._compat.typing import assert_never
from .architecture import Architecture
from .environment import ParsedEnvironment
@@ -24,18 +25,20 @@ from .options import Options
from .typing import PathOrStr
from .util import (
CIBW_CACHE_PATH,
AlreadyBuiltWheelError,
BuildFrontendConfig,
BuildFrontendName,
BuildSelector,
NonPlatformWheelError,
call,
combine_constraints,
detect_ci_provider,
download,
find_compatible_wheel,
find_uv,
free_thread_enable_313,
get_build_verbosity_extra_flags,
get_pip_version,
install_certifi_script,
move_file,
prepare_command,
read_python_configs,
shell,
@@ -46,6 +49,7 @@ from .util import (
)
@functools.lru_cache(maxsize=None)
def get_macos_version() -> tuple[int, int]:
"""
Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0)
@@ -57,9 +61,29 @@ def get_macos_version() -> tuple[int, int]:
"""
version_str, _, _ = platform.mac_ver()
version = tuple(map(int, version_str.split(".")[:2]))
if (10, 15) < version < (11, 0):
# When built against an older macOS SDK, Python will report macOS 10.16
# instead of the real version.
version_str = call(
sys.executable,
"-sS",
"-c",
"import platform; print(platform.mac_ver()[0])",
env={"SYSTEM_VERSION_COMPAT": "0"},
capture_stdout=True,
)
version = tuple(map(int, version_str.split(".")[:2]))
return typing.cast(Tuple[int, int], version)
@functools.lru_cache(maxsize=None)
def get_test_macosx_deployment_target() -> str:
version = get_macos_version()
if version >= (11, 0):
return f"{version[0]}.0"
return f"{version[0]}.{version[1]}"
def get_macos_sdks() -> list[str]:
output = call("xcodebuild", "-showsdks", capture_stdout=True)
return [m.group(1) for m in re.finditer(r"-sdk (macosx\S+)", output)]
@@ -114,35 +138,44 @@ def get_python_configurations(
return python_configurations
def install_cpython(tmp: Path, version: str, url: str) -> Path:
installation_path = Path(f"/Library/Frameworks/Python.framework/Versions/{version}")
def install_cpython(tmp: Path, version: str, url: str, free_threading: bool) -> Path:
ft = "T" if free_threading else ""
installation_path = Path(f"/Library/Frameworks/Python{ft}.framework/Versions/{version}")
with FileLock(CIBW_CACHE_PATH / f"cpython{version}.lock"):
installed_system_packages = call("pkgutil", "--pkgs", capture_stdout=True).splitlines()
# if this version of python isn't installed, get it from python.org and install
python_package_identifier = f"org.python.Python.PythonFramework-{version}"
python_package_identifier = f"org.python.Python.Python{ft}Framework-{version}"
if python_package_identifier not in installed_system_packages:
if detect_ci_provider() is None:
# if running locally, we don't want to install CPython with sudo
# let the user know & provide a link to the installer
print(
msg = (
f"Error: CPython {version} is not installed.\n"
"cibuildwheel will not perform system-wide installs when running outside of CI.\n"
f"To build locally, install CPython {version} on this machine, or, disable this version of Python using CIBW_SKIP=cp{version.replace('.', '')}-macosx_*\n"
f"\nDownload link: {url}",
file=sys.stderr,
f"\nDownload link: {url}"
)
raise SystemExit(1)
raise errors.FatalError(msg)
pkg_path = tmp / "Python.pkg"
# download the pkg
download(url, pkg_path)
# install
call("sudo", "installer", "-pkg", pkg_path, "-target", "/")
args = []
if version.startswith("3.13"):
# Python 3.13 is the first version to have a free-threading option
args += ["-applyChoiceChangesXML", str(free_thread_enable_313.resolve())]
call("sudo", "installer", "-pkg", pkg_path, *args, "-target", "/")
pkg_path.unlink()
env = os.environ.copy()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
call(installation_path / "bin" / "python3", install_certifi_script, env=env)
return installation_path / "bin" / "python3"
if free_threading:
call(installation_path / f"bin/python{version}t", "-m", "ensurepip", env=env)
call(installation_path / f"bin/python{version}t", install_certifi_script, env=env)
else:
call(installation_path / "bin/python3", install_certifi_script, env=env)
return installation_path / "bin" / (f"python{version}t" if free_threading else "python3")
def install_pypy(tmp: Path, url: str) -> Path:
@@ -166,23 +199,41 @@ def setup_python(
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
build_frontend: BuildFrontendName,
) -> dict[str, str]:
) -> tuple[Path, dict[str, str]]:
if build_frontend == "build[uv]" and Version(python_configuration.version) < Version("3.8"):
build_frontend = "build"
uv_path = find_uv()
use_uv = build_frontend == "build[uv]" and Version(python_configuration.version) >= Version(
"3.8"
)
tmp.mkdir()
implementation_id = python_configuration.identifier.split("-")[0]
log.step(f"Installing Python {implementation_id}...")
if implementation_id.startswith("cp"):
base_python = install_cpython(tmp, python_configuration.version, python_configuration.url)
free_threading = "t-macos" in python_configuration.identifier
base_python = install_cpython(
tmp, python_configuration.version, python_configuration.url, free_threading
)
elif implementation_id.startswith("pp"):
base_python = install_pypy(tmp, python_configuration.url)
else:
msg = "Unknown Python implementation"
raise ValueError(msg)
assert base_python.exists()
assert (
base_python.exists()
), f"{base_python.name} not found, has {list(base_python.parent.iterdir())}"
log.step("Setting up build environment...")
venv_path = tmp / "venv"
env = virtualenv(
python_configuration.version, base_python, venv_path, dependency_constraint_flags
python_configuration.version,
base_python,
venv_path,
dependency_constraint_flags,
use_uv=use_uv,
)
venv_bin_path = venv_path / "bin"
assert venv_bin_path.exists()
@@ -199,43 +250,43 @@ def setup_python(
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
call(
"python",
"-m",
"pip",
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
if build_frontend == "build[uv]":
assert uv_path is not None
pip = [str(uv_path), "pip"]
else:
pip = ["python", "-m", "pip"]
if not use_uv:
call(
*pip,
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
# Apply our environment after pip is ready
env = environment.as_dictionary(prev_environment=env)
# check what pip version we're on
assert (venv_bin_path / "pip").exists()
call("which", "pip", env=env)
call("pip", "--version", env=env)
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
if which_pip != str(venv_bin_path / "pip"):
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
if not use_uv:
assert (venv_bin_path / "pip").exists()
call("which", "pip", env=env)
call("pip", "--version", env=env)
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
if which_pip != str(venv_bin_path / "pip"):
msg = "cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
# check what Python version we're on
call("which", "python", env=env)
call("python", "--version", env=env)
which_python = call("which", "python", env=env, capture_stdout=True).strip()
if which_python != str(venv_bin_path / "python"):
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
msg = "cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg)
config_is_arm64 = python_configuration.identifier.endswith("arm64")
config_is_universal2 = python_configuration.identifier.endswith("universal2")
@@ -243,8 +294,25 @@ def setup_python(
# Set MACOSX_DEPLOYMENT_TARGET, if the user didn't set it.
# For arm64, the minimal deployment target is 11.0.
# On x86_64 (or universal2), use 10.9 as a default.
# PyPy defaults to 10.7, causing inconsistencies if it's left unset.
env.setdefault("MACOSX_DEPLOYMENT_TARGET", "11.0" if config_is_arm64 else "10.9")
# CPython 3.12.6+ needs 10.13.
if config_is_arm64:
default_target = "11.0"
elif Version(python_configuration.version) >= Version("3.12"):
default_target = "10.13"
elif python_configuration.identifier.startswith("pp") and Version(
python_configuration.version
) >= Version("3.9"):
default_target = "10.15"
else:
default_target = "10.9"
env.setdefault("MACOSX_DEPLOYMENT_TARGET", default_target)
# This is a floor, it can't be set lower than the default_target.
if Version(env["MACOSX_DEPLOYMENT_TARGET"]) < Version(default_target):
log.warning(
f"Bumping MACOSX_DEPLOYMENT_TARGET ({env['MACOSX_DEPLOYMENT_TARGET']}) to the minimum required ({default_target})."
)
env["MACOSX_DEPLOYMENT_TARGET"] = default_target
if python_configuration.version not in {"3.6", "3.7"}:
if config_is_arm64:
@@ -303,10 +371,22 @@ def setup_python(
*dependency_constraint_flags,
env=env,
)
elif build_frontend == "build[uv]":
assert uv_path is not None
call(
uv_path,
"pip",
"install",
"--upgrade",
"delocate",
"build[virtualenv, uv]",
*dependency_constraint_flags,
env=env,
)
else:
assert_never(build_frontend)
return env
return base_python, env
def build(options: Options, tmp_path: Path) -> None:
@@ -335,6 +415,14 @@ def build(options: Options, tmp_path: Path) -> None:
for config in python_configurations:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = build_frontend.name == "build[uv]" and Version(config.version) >= Version(
"3.8"
)
uv_path = find_uv()
if use_uv and uv_path is None:
msg = "uv not found"
raise AssertionError(msg)
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier
@@ -352,13 +440,15 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.dependency_constraints.get_for_python_version(config.version),
]
env = setup_python(
base_python, env = setup_python(
identifier_tmp_dir / "build",
config,
dependency_constraint_flags,
build_options.environment,
build_frontend.name,
)
if not use_uv:
pip_version = get_pip_version(env)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
@@ -384,15 +474,14 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags += build_frontend.args
build_env = env.copy()
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
if not use_uv:
build_env["VIRTUALENV_PIP"] = pip_version
if build_options.dependency_constraints:
constraint_path = build_options.dependency_constraints.get_for_python_version(
config.version
)
user_constraints = build_env.get("PIP_CONSTRAINT")
our_constraints = constraint_path.as_uri()
build_env["PIP_CONSTRAINT"] = " ".join(
c for c in [user_constraints, our_constraints] if c
combine_constraints(
build_env, constraint_path, identifier_tmp_dir if use_uv else None
)
if build_frontend.name == "pip":
@@ -410,10 +499,12 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=build_env,
)
elif build_frontend.name == "build":
elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags.append("--installer=uv")
call(
"python",
"-m",
@@ -432,7 +523,7 @@ def build(options: Options, tmp_path: Path) -> None:
repaired_wheel_dir.mkdir()
if built_wheel.name.endswith("none-any.whl"):
raise NonPlatformWheelError()
raise errors.NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
@@ -457,7 +548,7 @@ def build(options: Options, tmp_path: Path) -> None:
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise AlreadyBuiltWheelError(repaired_wheel.name)
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
log.step_end()
@@ -543,15 +634,18 @@ def build(options: Options, tmp_path: Path) -> None:
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
if not use_uv:
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
venv_dir = identifier_tmp_dir / f"venv-test-{testing_arch}"
arch_prefix = []
uv_arch_args = []
if testing_arch != machine_arch:
if machine_arch == "arm64" and testing_arch == "x86_64":
# rosetta2 will provide the emulation with just the arch prefix.
arch_prefix = ["arch", "-x86_64"]
uv_arch_args = ["--python-platform", "x86_64-apple-darwin"]
else:
msg = f"don't know how to emulate {testing_arch} on {machine_arch}"
raise RuntimeError(msg)
@@ -560,25 +654,31 @@ def build(options: Options, tmp_path: Path) -> None:
call_with_arch = functools.partial(call, *arch_prefix)
shell_with_arch = functools.partial(call, *arch_prefix, "/bin/sh", "-c")
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
call_with_arch("python", "-m", "virtualenv", "--no-download", venv_dir, env=env)
if use_uv:
pip_install = functools.partial(call, *pip, "install", *uv_arch_args)
call("uv", "venv", venv_dir, f"--python={base_python}", env=env)
else:
pip_install = functools.partial(call_with_arch, *pip, "install")
# Use pip version from the initial env to ensure determinism
venv_args = ["--no-periodic-update", f"--pip={pip_version}"]
# In Python<3.12, setuptools & wheel are installed as well, use virtualenv embedded ones
if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
call_with_arch("python", "-m", "virtualenv", *venv_args, venv_dir, env=env)
virtualenv_env = env.copy()
virtualenv_env["MACOSX_DEPLOYMENT_TARGET"] = get_test_macosx_deployment_target()
virtualenv_env["PATH"] = os.pathsep.join(
[
str(venv_dir / "bin"),
virtualenv_env["PATH"],
]
)
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
# check that we are using the Python from the virtual environment
call_with_arch("which", "python", env=virtualenv_env)
# TODO remove me once virtualenv provides pip>=24.1b1
if config.version == "3.13":
call("python", "-m", "pip", "install", "pip>=24.1b1", env=virtualenv_env)
if build_options.before_test:
before_test_prepared = prepare_command(
build_options.before_test,
@@ -605,18 +705,14 @@ def build(options: Options, tmp_path: Path) -> None:
else:
virtualenv_env_install_wheel = virtualenv_env
call_with_arch(
"pip",
"install",
pip_install(
f"{repaired_wheel}{build_options.test_extras}",
env=virtualenv_env_install_wheel,
)
# test the wheel
if build_options.test_requires:
call_with_arch(
"pip",
"install",
pip_install(
*build_options.test_requires,
env=virtualenv_env_install_wheel,
)
@@ -639,18 +735,18 @@ def build(options: Options, tmp_path: Path) -> None:
# we're all done here; move it to output (overwrite existing)
if compatible_wheel is None:
with contextlib.suppress(FileNotFoundError):
(build_options.output_dir / repaired_wheel.name).unlink()
shutil.move(str(repaired_wheel), build_options.output_dir)
built_wheels.append(build_options.output_dir / repaired_wheel.name)
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
# clean up
shutil.rmtree(identifier_tmp_dir)
log.build_end()
except subprocess.CalledProcessError as error:
log.step_end_with_error(
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
)
sys.exit(1)
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
raise errors.FatalError(msg) from error
+100 -68
View File
@@ -5,18 +5,22 @@ import json
import os
import platform
import shlex
import shutil
import subprocess
import sys
import typing
import uuid
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path, PurePath, PurePosixPath
from types import TracebackType
from typing import IO, Dict, Literal
from ._compat.typing import Self
from packaging.version import InvalidVersion, Version
from ._compat.typing import Self, assert_never
from .errors import OCIEngineTooOldError
from .logger import log
from .typing import PathOrStr, PopenBytes
from .util import (
CIProvider,
@@ -29,6 +33,15 @@ from .util import (
ContainerEngineName = Literal["docker", "podman"]
# Order of the enum matters for tests. 386 shall appear before amd64.
class OCIPlatform(Enum):
i386 = "linux/386"
AMD64 = "linux/amd64"
ARM64 = "linux/arm64"
PPC64LE = "linux/ppc64le"
S390X = "linux/s390x"
@dataclass(frozen=True)
class OCIContainerEngineConfig:
name: ContainerEngineName
@@ -56,6 +69,15 @@ class OCIContainerEngineConfig:
disable_host_mount = (
strtobool(disable_host_mount_options[-1]) if disable_host_mount_options else False
)
if "--platform" in create_args or any(arg.startswith("--platform=") for arg in create_args):
msg = "Using '--platform' in 'container-engine::create_args' is deprecated. It will be ignored."
log.warning(msg)
if "--platform" in create_args:
index = create_args.index("--platform")
create_args.pop(index)
create_args.pop(index)
else:
create_args = [arg for arg in create_args if not arg.startswith("--platform=")]
return OCIContainerEngineConfig(
name=name, create_args=tuple(create_args), disable_host_mount=disable_host_mount
@@ -75,6 +97,32 @@ class OCIContainerEngineConfig:
DEFAULT_ENGINE = OCIContainerEngineConfig("docker")
def _check_engine_version(engine: OCIContainerEngineConfig) -> None:
try:
version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True)
version_info = json.loads(version_string.strip())
if engine.name == "docker":
# --platform support was introduced in 1.32 as experimental
# docker cp, as used by cibuildwheel, has been fixed in v24 => API 1.43, https://github.com/moby/moby/issues/38995
client_api_version = Version(version_info["Client"]["ApiVersion"])
engine_api_version = Version(version_info["Server"]["ApiVersion"])
version_supported = min(client_api_version, engine_api_version) >= Version("1.43")
elif engine.name == "podman":
client_api_version = Version(version_info["Client"]["APIVersion"])
if "Server" in version_info:
engine_api_version = Version(version_info["Server"]["APIVersion"])
else:
engine_api_version = client_api_version
# --platform support was introduced in v3
version_supported = min(client_api_version, engine_api_version) >= Version("3")
else:
assert_never(engine.name)
if not version_supported:
raise OCIEngineTooOldError() from None
except (subprocess.CalledProcessError, KeyError, InvalidVersion) as e:
raise OCIEngineTooOldError() from e
class OCIContainer:
"""
An object that represents a running OCI (e.g. Docker) container.
@@ -108,7 +156,7 @@ class OCIContainer:
self,
*,
image: str,
enforce_32_bit: bool = False,
oci_platform: OCIPlatform,
cwd: PathOrStr | None = None,
engine: OCIContainerEngineConfig = DEFAULT_ENGINE,
):
@@ -117,14 +165,41 @@ class OCIContainer:
raise ValueError(msg)
self.image = image
self.enforce_32_bit = enforce_32_bit
self.oci_platform = oci_platform
self.cwd = cwd
self.name: str | None = None
self.engine = engine
def _get_platform_args(self, *, oci_platform: OCIPlatform | None = None) -> tuple[str, str]:
if oci_platform is None:
oci_platform = self.oci_platform
# we need '--pull=always' otherwise some images with the wrong platform get re-used (e.g. 386 image for amd64)
# c.f. https://github.com/moby/moby/issues/48197#issuecomment-2282802313
pull = "always"
try:
image_platform = call(
self.engine.name,
"image",
"inspect",
self.image,
"--format",
"{{.Os}}/{{.Architecture}}",
capture_stdout=True,
).strip()
if image_platform == oci_platform.value:
# in case the correct image is already present, don't pull
# this allows to run local only images
pull = "never"
except subprocess.CalledProcessError:
pass
return f"--platform={oci_platform.value}", f"--pull={pull}"
def __enter__(self) -> Self:
self.name = f"cibuildwheel-{uuid.uuid4()}"
_check_engine_version(self.engine)
# work-around for Travis-CI PPC64le Docker runs since 2021:
# this avoids network splits
# https://github.com/pypa/cibuildwheel/issues/904
@@ -133,14 +208,26 @@ class OCIContainer:
if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le":
network_args = ["--network=host"]
platform_args = self._get_platform_args()
simulate_32_bit = False
if self.enforce_32_bit:
if self.oci_platform == OCIPlatform.i386:
# If the architecture running the image is already the right one
# or the image entrypoint takes care of enforcing this, then we don't need to
# simulate this
container_machine = call(
self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True
).strip()
run_cmd = [self.engine.name, "run", "--rm"]
ctr_cmd = ["uname", "-m"]
try:
container_machine = call(
*run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
).strip()
except subprocess.CalledProcessError:
# The image might have been built with amd64 architecture
# Let's try that
platform_args = self._get_platform_args(oci_platform=OCIPlatform.AMD64)
container_machine = call(
*run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
).strip()
simulate_32_bit = container_machine != "i686"
shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"]
@@ -155,6 +242,7 @@ class OCIContainer:
"--interactive",
*(["--volume=/:/host"] if not self.engine.disable_host_mount else []),
*network_args,
*platform_args,
*self.engine.create_args,
self.image,
*shell_args,
@@ -221,73 +309,17 @@ class OCIContainer:
self.name = None
def copy_into(self, from_path: Path, to_path: PurePath) -> None:
# `docker cp` causes 'no space left on device' error when
# a container is running and the host filesystem is
# mounted. https://github.com/moby/moby/issues/38995
# Use `docker exec` instead.
if from_path.is_dir():
self.call(["mkdir", "-p", to_path])
subprocess.run(
f"tar cf - . | {self.engine.name} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -",
shell=True,
check=True,
cwd=from_path,
)
call(self.engine.name, "cp", f"{from_path}/.", f"{self.name}:{to_path}")
else:
exec_process: subprocess.Popen[bytes]
with subprocess.Popen(
[
self.engine.name,
"exec",
"-i",
str(self.name),
"sh",
"-c",
f"cat > {shell_quote(to_path)}",
],
stdin=subprocess.PIPE,
) as exec_process:
assert exec_process.stdin
with open(from_path, "rb") as from_file:
# Bug in mypy, https://github.com/python/mypy/issues/15031
shutil.copyfileobj(from_file, exec_process.stdin) # type: ignore[misc]
exec_process.stdin.close()
exec_process.wait()
if exec_process.returncode:
raise subprocess.CalledProcessError(
exec_process.returncode, exec_process.args, None, None
)
self.call(["mkdir", "-p", to_path.parent])
call(self.engine.name, "cp", from_path, f"{self.name}:{to_path}")
def copy_out(self, from_path: PurePath, to_path: Path) -> None:
# note: we assume from_path is a dir
to_path.mkdir(parents=True, exist_ok=True)
if self.engine.name == "podman":
subprocess.run(
[
self.engine.name,
"cp",
f"{self.name}:{from_path}/.",
str(to_path),
],
check=True,
cwd=to_path,
)
elif self.engine.name == "docker":
# There is a bug in docker that prevents a simple 'cp' invocation
# from working https://github.com/moby/moby/issues/38995
command = f"{self.engine.name} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -"
subprocess.run(
command,
shell=True,
check=True,
cwd=to_path,
)
else:
raise KeyError(self.engine.name)
call(self.engine.name, "cp", f"{self.name}:{from_path}/.", to_path)
def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]:
glob_pattern = path.joinpath(pattern)
+197 -112
View File
@@ -8,17 +8,16 @@ import difflib
import enum
import functools
import shlex
import sys
import textwrap
import traceback
from collections.abc import Callable, Generator, Iterable, Iterator, Set
from collections.abc import Generator, Iterable, Set
from pathlib import Path
from typing import Any, Literal, Mapping, Sequence, TypedDict, Union # noqa: TID251
from typing import Any, Literal, Mapping, Sequence, Union # noqa: TID251
from packaging.specifiers import SpecifierSet
from . import errors
from ._compat import tomllib
from ._compat.typing import NotRequired, assert_never
from ._compat.typing import assert_never
from .architecture import Architecture
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
from .logger import log
@@ -32,7 +31,6 @@ from .util import (
BuildSelector,
DependencyConstraints,
TestSelector,
cached_property,
format_safe,
resources_dir,
selector_matches,
@@ -52,6 +50,7 @@ class CommandLineArguments:
print_build_identifiers: bool
allow_empty: bool
prerelease_pythons: bool
debug_traceback: bool
@staticmethod
def defaults() -> CommandLineArguments:
@@ -65,6 +64,7 @@ class CommandLineArguments:
package_dir=Path("."),
prerelease_pythons=False,
print_build_identifiers=False,
debug_traceback=False,
)
@@ -75,6 +75,7 @@ class GlobalOptions:
build_selector: BuildSelector
test_selector: TestSelector
architectures: set[Architecture]
allow_empty: bool
@dataclasses.dataclass(frozen=True)
@@ -117,13 +118,16 @@ class BuildOptions:
return self.globals.architectures
Setting = Union[Mapping[str, str], Sequence[str], str, int, bool]
SettingLeaf = Union[str, int, bool]
SettingList = Sequence[SettingLeaf]
SettingTable = Mapping[str, Union[SettingLeaf, SettingList]]
SettingValue = Union[SettingTable, SettingList, SettingLeaf]
@dataclasses.dataclass(frozen=True)
class Override:
select_pattern: str
options: dict[str, Setting]
options: dict[str, SettingValue]
inherit: dict[str, InheritRule]
@@ -136,22 +140,123 @@ DISALLOWED_OPTIONS = {
}
class TableFmt(TypedDict):
# a format string, used with '.format', with `k` and `v` parameters
# e.g. "{k}={v}"
item: str
# the string that is inserted between items
# e.g. " "
sep: str
# a quoting function that, if supplied, is called to quote each value
# e.g. shlex.quote
quote: NotRequired[Callable[[str], str]]
class ConfigOptionError(KeyError):
class OptionsReaderError(errors.ConfigurationError):
pass
class OptionFormat:
"""
Base class for option format specifiers. These objects describe how values
can be parsed from rich TOML values and how they're merged together.
"""
class NotSupported(Exception):
pass
def format_list(self, value: SettingList) -> str: # noqa: ARG002
raise OptionFormat.NotSupported
def format_table(self, table: SettingTable) -> str: # noqa: ARG002
raise OptionFormat.NotSupported
def merge_values(self, before: str, after: str) -> str: # noqa: ARG002
raise OptionFormat.NotSupported
class ListFormat(OptionFormat):
"""
A format that joins lists with a separator.
"""
def __init__(self, sep: str) -> None:
self.sep = sep
def format_list(self, value: SettingList) -> str:
return self.sep.join(str(v) for v in value)
def merge_values(self, before: str, after: str) -> str:
return f"{before}{self.sep}{after}"
class ShlexTableFormat(OptionFormat):
"""
The standard table format uses shlex.quote to quote values and shlex.split
to unquote and split them. When merging values, keys in before are
replaced by keys in after.
"""
def __init__(self, sep: str = " ", pair_sep: str = "=", allow_merge: bool = True) -> None:
self.sep = sep
self.pair_sep = pair_sep
self.allow_merge = allow_merge
def format_table(self, table: SettingTable) -> str:
assignments: list[tuple[str, str]] = []
for k, v in table.items():
if shlex.split(k) != [k]:
msg = f"Invalid table key: {k}"
raise OptionsReaderError(msg)
if isinstance(v, str):
assignments.append((k, v))
elif isinstance(v, Sequence):
for inner_v in v:
assignments.append((k, str(inner_v)))
else:
assignments.append((k, str(v)))
return self.sep.join(f"{k}{self.pair_sep}{shlex.quote(v)}" for k, v in assignments)
def merge_values(self, before: str, after: str) -> str:
if not self.allow_merge:
raise OptionFormat.NotSupported
before_dict = self.parse_table(before)
after_dict = self.parse_table(after)
return self.format_table({**before_dict, **after_dict})
def parse_table(self, table: str) -> Mapping[str, str | Sequence[str]]:
assignments: list[tuple[str, str]] = []
for assignment_str in shlex.split(table):
key, sep, value = assignment_str.partition(self.pair_sep)
if not sep:
msg = f"malformed option with value {assignment_str!r}"
raise OptionsReaderError(msg)
assignments.append((key, value))
result: dict[str, str | list[str]] = {}
for key, value in assignments:
if key in result:
existing_value = result[key]
if isinstance(existing_value, list):
result[key] = [*existing_value, value]
else:
result[key] = [existing_value, value]
else:
result[key] = value
return result
class EnvironmentFormat(OptionFormat):
"""
The environment format accepts a table of environment variables, where the
values may contain variables or command substitutions.
"""
def format_table(self, table: SettingTable) -> str:
return " ".join(f'{k}="{v}"' for k, v in table.items())
def merge_values(self, before: str, after: str) -> str:
return f"{before} {after}"
class InheritRule(enum.Enum):
NONE = enum.auto()
APPEND = enum.auto()
@@ -159,10 +264,9 @@ class InheritRule(enum.Enum):
def _resolve_cascade(
*pairs: tuple[Setting | None, InheritRule],
*pairs: tuple[SettingValue | None, InheritRule],
ignore_empty: bool = False,
list_sep: str | None = None,
table_format: TableFmt | None = None,
option_format: OptionFormat | None = None,
) -> str:
"""
Given a cascade of values with inherit rules, resolve them into a single
@@ -185,13 +289,6 @@ def _resolve_cascade(
result: str | None = None
if table_format is not None:
merge_sep = table_format["sep"]
elif list_sep is not None:
merge_sep = list_sep
else:
merge_sep = None
for value, rule in pairs:
if value is None:
continue
@@ -199,14 +296,9 @@ def _resolve_cascade(
if ignore_empty and not value and value is not False:
continue
value_string = _stringify_setting(value, list_sep, table_format)
value_string = _stringify_setting(value, option_format=option_format)
result = _merge_values(
result,
value_string,
rule=rule,
merge_sep=merge_sep,
)
result = _apply_inherit_rule(result, value_string, rule=rule, option_format=option_format)
if result is None:
msg = "a setting should at least have a default value"
@@ -216,7 +308,9 @@ def _resolve_cascade(
# pylint: disable-next=inconsistent-return-statements
def _merge_values(before: str | None, after: str, rule: InheritRule, merge_sep: str | None) -> str:
def _apply_inherit_rule(
before: str | None, after: str, rule: InheritRule, option_format: OptionFormat | None
) -> str:
if rule == InheritRule.NONE:
return after
@@ -229,34 +323,39 @@ def _merge_values(before: str | None, after: str, rule: InheritRule, merge_sep:
# if after is an empty string, we shouldn't add any separator
return before
if not merge_sep:
if not option_format:
msg = f"Don't know how to merge {before!r} and {after!r} with {rule}"
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
if rule == InheritRule.APPEND:
return f"{before}{merge_sep}{after}"
return option_format.merge_values(before, after)
elif rule == InheritRule.PREPEND:
return f"{after}{merge_sep}{before}"
return option_format.merge_values(after, before)
else:
assert_never(rule)
def _stringify_setting(
setting: Setting, list_sep: str | None, table_format: TableFmt | None
setting: SettingValue,
option_format: OptionFormat | None,
) -> str:
if isinstance(setting, Mapping):
if table_format is None:
try:
if option_format is None:
raise OptionFormat.NotSupported
return option_format.format_table(setting)
except OptionFormat.NotSupported:
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a table"
raise ConfigOptionError(msg)
return table_format["sep"].join(
item for k, v in setting.items() for item in _inner_fmt(k, v, table_format)
)
raise OptionsReaderError(msg) from None
if not isinstance(setting, str) and isinstance(setting, Sequence):
if list_sep is None:
try:
if option_format is None:
raise OptionFormat.NotSupported
return option_format.format_list(setting)
except OptionFormat.NotSupported:
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list"
raise ConfigOptionError(msg)
return list_sep.join(setting)
raise OptionsReaderError(msg) from None
if isinstance(setting, (bool, int)):
return str(setting)
@@ -322,14 +421,14 @@ class OptionsReader:
if config_overrides is not None:
if not isinstance(config_overrides, list):
msg = "'tool.cibuildwheel.overrides' must be a list"
raise ConfigOptionError(msg)
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 ConfigOptionError(msg)
raise OptionsReaderError(msg)
if isinstance(select, list):
select = " ".join(select)
@@ -339,7 +438,7 @@ class OptionsReader:
i in {"none", "append", "prepend"} for i in inherit.values()
):
msg = "'inherit' must be a dict containing only {'none', 'append', 'prepend'} values"
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
inherit_enum = {k: InheritRule[v.upper()] for k, v in inherit.items()}
@@ -357,7 +456,7 @@ class OptionsReader:
matches = difflib.get_close_matches(name, allowed_option_names, 1, 0.7)
if matches:
msg += f" Perhaps you meant {matches[0]!r}?"
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
def _validate_platform_option(self, name: str) -> None:
"""
@@ -367,7 +466,7 @@ class OptionsReader:
disallowed_platform_options = self.disallow.get(self.platform, set())
if name in disallowed_platform_options:
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
@@ -376,7 +475,7 @@ class OptionsReader:
matches = difflib.get_close_matches(name, allowed_option_names, 1, 0.7)
if matches:
msg += f" Perhaps you meant {matches[0]!r}?"
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
def _load_file(self, filename: Path) -> tuple[dict[str, Any], dict[str, Any]]:
"""
@@ -411,8 +510,7 @@ class OptionsReader:
name: str,
*,
env_plat: bool = True,
list_sep: str | None = None,
table_format: TableFmt | None = None,
option_format: OptionFormat | None = None,
ignore_empty: bool = False,
) -> str:
"""
@@ -428,7 +526,7 @@ class OptionsReader:
if name not in self.default_options and name not in self.default_platform_options:
msg = f"{name!r} must be in cibuildwheel/resources/defaults.toml file to be accessed."
raise ConfigOptionError(msg)
raise OptionsReaderError(msg)
# Environment variable form
envvar = f"CIBW_{name.upper().replace('-', '_')}"
@@ -448,26 +546,10 @@ class OptionsReader:
(self.env.get(envvar), InheritRule.NONE),
(self.env.get(plat_envvar) if env_plat else None, InheritRule.NONE),
ignore_empty=ignore_empty,
list_sep=list_sep,
table_format=table_format,
option_format=option_format,
)
def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
quote_function = table.get("quote", lambda a: a)
if isinstance(v, list):
for inner_v in v:
qv = quote_function(inner_v)
yield table["item"].format(k=k, v=qv)
elif isinstance(v, bool):
qv = quote_function(str(v))
yield table["item"].format(k=k, v=qv)
else:
qv = quote_function(v)
yield table["item"].format(k=k, v=qv)
class Options:
def __init__(
self,
@@ -501,7 +583,7 @@ class Options:
return None
@cached_property
@functools.cached_property
def package_requires_python_str(self) -> str | None:
args = self.command_line_arguments
return get_requires_python_str(Path(args.package_dir))
@@ -512,14 +594,18 @@ class Options:
package_dir = args.package_dir
output_dir = args.output_dir
build_config = self.reader.get("build", env_plat=False, list_sep=" ") or "*"
skip_config = self.reader.get("skip", env_plat=False, list_sep=" ")
test_skip = self.reader.get("test-skip", env_plat=False, list_sep=" ")
build_config = (
self.reader.get("build", env_plat=False, option_format=ListFormat(sep=" ")) or "*"
)
skip_config = self.reader.get("skip", env_plat=False, option_format=ListFormat(sep=" "))
test_skip = self.reader.get("test-skip", env_plat=False, option_format=ListFormat(sep=" "))
free_threaded_support = strtobool(
self.reader.get("free-threaded-support", env_plat=False, ignore_empty=True)
)
allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))
prerelease_pythons = args.prerelease_pythons or strtobool(
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
)
@@ -531,7 +617,7 @@ class Options:
)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
archs_config_str = args.archs or self.reader.get("archs", list_sep=" ")
archs_config_str = args.archs or self.reader.get("archs", option_format=ListFormat(sep=" "))
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
# Process `--only`
@@ -557,6 +643,7 @@ class Options:
build_selector=build_selector,
test_selector=test_selector,
architectures=architectures,
allow_empty=allow_empty,
)
def build_options(self, identifier: str | None) -> BuildOptions:
@@ -565,30 +652,33 @@ class Options:
"""
with self.reader.identifier(identifier):
before_all = self.reader.get("before-all", list_sep=" && ")
before_all = self.reader.get("before-all", option_format=ListFormat(sep=" && "))
environment_config = self.reader.get(
"environment", table_format={"item": '{k}="{v}"', "sep": " "}
environment_config = self.reader.get("environment", option_format=EnvironmentFormat())
environment_pass = self.reader.get(
"environment-pass", option_format=ListFormat(sep=" ")
).split()
before_build = self.reader.get("before-build", option_format=ListFormat(sep=" && "))
repair_command = self.reader.get(
"repair-wheel-command", option_format=ListFormat(sep=" && ")
)
environment_pass = self.reader.get("environment-pass", list_sep=" ").split()
before_build = self.reader.get("before-build", list_sep=" && ")
repair_command = self.reader.get("repair-wheel-command", list_sep=" && ")
config_settings = self.reader.get(
"config-settings",
table_format={"item": "{k}={v}", "sep": " ", "quote": shlex.quote},
"config-settings", option_format=ShlexTableFormat(sep=" ", pair_sep="=")
)
dependency_versions = self.reader.get("dependency-versions")
test_command = self.reader.get("test-command", list_sep=" && ")
before_test = self.reader.get("before-test", list_sep=" && ")
test_requires = self.reader.get("test-requires", list_sep=" ").split()
test_extras = self.reader.get("test-extras", list_sep=",")
test_command = self.reader.get("test-command", option_format=ListFormat(sep=" && "))
before_test = self.reader.get("before-test", option_format=ListFormat(sep=" && "))
test_requires = self.reader.get(
"test-requires", option_format=ListFormat(sep=" ")
).split()
test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=","))
build_verbosity_str = self.reader.get("build-verbosity")
build_frontend_str = self.reader.get(
"build-frontend",
env_plat=False,
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
)
build_frontend: BuildFrontendConfig | None
if not build_frontend_str or build_frontend_str == "default":
@@ -597,18 +687,14 @@ class Options:
try:
build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str)
except ValueError as e:
print(f"cibuildwheel: {e}", file=sys.stderr)
sys.exit(2)
msg = f"Failed to parse build frontend. {e}"
raise errors.ConfigurationError(msg) from e
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print(
f"cibuildwheel: Malformed environment option {environment_config!r}",
file=sys.stderr,
)
traceback.print_exc(None, sys.stderr)
sys.exit(2)
except (EnvironmentParseError, ValueError) as e:
msg = f"Malformed environment option {environment_config!r}"
raise errors.ConfigurationError(msg) from e
# Pass through environment variables
if self.platform == "linux":
@@ -674,15 +760,14 @@ class Options:
container_engine_str = self.reader.get(
"container-engine",
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
)
try:
container_engine = OCIContainerEngineConfig.from_config_string(container_engine_str)
except ValueError as e:
msg = f"cibuildwheel: Failed to parse container config. {e}"
print(msg, file=sys.stderr)
sys.exit(2)
msg = f"Failed to parse container config. {e}"
raise errors.ConfigurationError(msg) from e
return BuildOptions(
globals=self.globals,
@@ -726,7 +811,7 @@ class Options:
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
@cached_property
@functools.cached_property
def defaults(self) -> Options:
return Options(
platform=self.platform,
@@ -862,6 +947,6 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
if "p2" in selector or "p35" in selector:
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
print(msg, file=sys.stderr)
if error:
sys.exit(4)
raise errors.DeprecationError(msg)
log.warning(msg)
+1 -3
View File
@@ -39,10 +39,8 @@ def is_main(parent: ast.AST | None) -> bool:
if len(mains) != 1:
return False
consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
if len(consts) != 1:
return False
return True
return len(consts) == 1
class Analyzer(ast.NodeVisitor):
+399
View File
@@ -0,0 +1,399 @@
from __future__ import annotations
import os
import shutil
import sys
from collections.abc import Sequence, Set
from dataclasses import dataclass
from pathlib import Path
from filelock import FileLock
from . import errors
from .architecture import Architecture
from .environment import ParsedEnvironment
from .logger import log
from .options import Options
from .typing import PathOrStr
from .util import (
CIBW_CACHE_PATH,
BuildFrontendConfig,
BuildSelector,
call,
combine_constraints,
download,
ensure_node,
extract_zip,
find_compatible_wheel,
get_pip_version,
move_file,
prepare_command,
read_python_configs,
shell,
split_config_settings,
test_fail_cwd_file,
virtualenv,
)
@dataclass(frozen=True)
class PythonConfiguration:
version: str
identifier: str
pyodide_version: str
emscripten_version: str
node_version: str
def install_emscripten(tmp: Path, version: str) -> Path:
# We don't need to match the emsdk version to the version we install, but
# we do for stability
url = f"https://github.com/emscripten-core/emsdk/archive/refs/tags/{version}.zip"
installation_path = CIBW_CACHE_PATH / f"emsdk-{version}"
emsdk_path = installation_path / f"emsdk-{version}/emsdk"
emcc_path = installation_path / f"emsdk-{version}/upstream/emscripten/emcc"
with FileLock(f"{installation_path}.lock"):
if installation_path.exists():
return emcc_path
emsdk_zip = tmp / "emsdk.zip"
download(url, emsdk_zip)
installation_path.mkdir()
extract_zip(emsdk_zip, installation_path)
call(emsdk_path, "install", version)
call(emsdk_path, "activate", version)
return emcc_path
def install_xbuildenv(env: dict[str, str], pyodide_version: str) -> str:
pyodide_root = (
CIBW_CACHE_PATH
/ f".pyodide-xbuildenv-{pyodide_version}/{pyodide_version}/xbuildenv/pyodide-root"
)
with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"):
if pyodide_root.exists():
return str(pyodide_root)
# We don't want to mutate env but we need to delete any existing
# PYODIDE_ROOT so copy it first.
env = dict(env)
env.pop("PYODIDE_ROOT", None)
call(
"pyodide",
"xbuildenv",
"install",
pyodide_version,
env=env,
cwd=CIBW_CACHE_PATH,
)
return str(pyodide_root)
def get_base_python(identifier: str) -> Path:
implementation_id = identifier.split("-")[0]
majorminor = implementation_id[len("cp") :]
version_info = (int(majorminor[0]), int(majorminor[1:]))
if version_info == sys.version_info[:2]:
return Path(sys.executable)
major_minor = ".".join(str(v) for v in version_info)
python_name = f"python{major_minor}"
which_python = shutil.which(python_name)
if which_python is None:
msg = f"CPython {major_minor} is not installed."
raise errors.FatalError(msg)
return Path(which_python)
def setup_python(
tmp: Path,
python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
) -> dict[str, str]:
base_python = get_base_python(python_configuration.identifier)
log.step("Setting up build environment...")
venv_path = tmp / "venv"
env = virtualenv(python_configuration.version, base_python, venv_path, [], use_uv=False)
venv_bin_path = venv_path / "bin"
assert venv_bin_path.exists()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
call(
"python",
"-m",
"pip",
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
env = environment.as_dictionary(prev_environment=env)
# check what pip version we're on
assert (venv_bin_path / "pip").exists()
call("which", "pip", env=env)
call("pip", "--version", env=env)
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
if which_pip != str(venv_bin_path / "pip"):
msg = "pip available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
# check what Python version we're on
call("which", "python", env=env)
call("python", "--version", env=env)
which_python = call("which", "python", env=env, capture_stdout=True).strip()
if which_python != str(venv_bin_path / "python"):
msg = "python available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg)
log.step("Installing build tools...")
call(
"pip",
"install",
"--upgrade",
"auditwheel-emscripten",
"build[virtualenv]",
"pyodide-build",
*dependency_constraint_flags,
env=env,
)
log.step("Installing emscripten...")
emcc_path = install_emscripten(tmp, python_configuration.emscripten_version)
env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]])
log.step("Installing Pyodide xbuildenv...")
env["PYODIDE_ROOT"] = install_xbuildenv(env, python_configuration.pyodide_version)
return env
def get_python_configurations(
build_selector: BuildSelector,
architectures: Set[Architecture], # noqa: ARG001
) -> list[PythonConfiguration]:
full_python_configs = read_python_configs("pyodide")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
return python_configurations
def build(options: Options, tmp_path: Path) -> None:
python_configurations = get_python_configurations(
options.globals.build_selector, options.globals.architectures
)
if not python_configurations:
return
try:
before_all_options_identifier = python_configurations[0].identifier
before_all_options = options.build_options(before_all_options_identifier)
if before_all_options.before_all:
log.step("Running before_all...")
env = before_all_options.environment.as_dictionary(prev_environment=os.environ)
before_all_prepared = prepare_command(
before_all_options.before_all, project=".", package=before_all_options.package_dir
)
shell(before_all_prepared, env=env)
built_wheels: list[Path] = []
for config in python_configurations:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("build")
if build_frontend.name == "pip":
msg = "The pyodide platform doesn't support pip frontend"
raise errors.FatalError(msg)
log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier
built_wheel_dir = identifier_tmp_dir / "built_wheel"
repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel"
identifier_tmp_dir.mkdir()
built_wheel_dir.mkdir()
repaired_wheel_dir.mkdir()
dependency_constraint_flags: Sequence[PathOrStr] = []
if build_options.dependency_constraints:
constraints_path = build_options.dependency_constraints.get_for_python_version(
config.version, variant="pyodide"
)
dependency_constraint_flags = ["-c", constraints_path]
env = setup_python(
identifier_tmp_dir / "build",
config,
dependency_constraint_flags,
build_options.environment,
)
pip_version = get_pip_version(env)
# The Pyodide command line runner mounts all directories in the host
# filesystem into the Pyodide file system, except for the custom
# file systems /dev, /lib, /proc, and /tmp. Mounting the mount
# points for alternate file systems causes some mysterious failure
# of the process (it just quits without any clear error).
#
# Because of this, by default Pyodide can't see anything under /tmp.
# This environment variable tells it also to mount our temp
# directory.
oldmounts = ""
extra_mounts = [str(identifier_tmp_dir)]
if str(Path(".").resolve()).startswith("/tmp"):
extra_mounts.append(str(Path(".").resolve()))
if "_PYODIDE_EXTRA_MOUNTS" in env:
oldmounts = env["_PYODIDE_EXTRA_MOUNTS"] + ":"
env["_PYODIDE_EXTRA_MOUNTS"] = oldmounts + ":".join(extra_mounts)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
log.step_end()
print(
f"\nFound previously built wheel {compatible_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
)
built_wheel = compatible_wheel
else:
if build_options.before_build:
log.step("Running before_build...")
before_build_prepared = prepare_command(
build_options.before_build, project=".", package=build_options.package_dir
)
shell(before_build_prepared, env=env)
log.step("Building wheel...")
extra_flags = split_config_settings(build_options.config_settings, "build")
extra_flags += build_frontend.args
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
build_env = env.copy()
if build_options.dependency_constraints:
combine_constraints(build_env, constraints_path, identifier_tmp_dir)
build_env["VIRTUALENV_PIP"] = pip_version
call(
"pyodide",
"build",
build_options.package_dir,
f"--outdir={built_wheel_dir}",
*extra_flags,
env=build_env,
)
built_wheel = next(built_wheel_dir.glob("*.whl"))
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command,
wheel=built_wheel,
dest_dir=repaired_wheel_dir,
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
if build_options.test_command and build_options.test_selector(config.identifier):
log.step("Testing wheel...")
venv_dir = identifier_tmp_dir / "venv-test"
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
virtualenv_env = env.copy()
virtualenv_env["PATH"] = os.pathsep.join(
[
str(ensure_node(config.node_version)),
virtualenv_env["PATH"],
]
)
# pyodide venv uses virtualenv under the hood
# use the pip embedded with virtualenv & disable network updates
virtualenv_create_env = virtualenv_env.copy()
virtualenv_create_env["VIRTUALENV_PIP"] = pip_version
virtualenv_create_env["VIRTUALENV_NO_PERIODIC_UPDATE"] = "1"
call("pyodide", "venv", venv_dir, env=virtualenv_create_env)
virtualenv_env["PATH"] = os.pathsep.join(
[
str(venv_dir / "bin"),
virtualenv_env["PATH"],
]
)
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
# check that we are using the Python from the virtual environment
call("which", "python", env=virtualenv_env)
if build_options.before_test:
before_test_prepared = prepare_command(
build_options.before_test,
project=".",
package=build_options.package_dir,
)
shell(before_test_prepared, env=virtualenv_env)
# install the wheel
call(
"pip",
"install",
f"{repaired_wheel}{build_options.test_extras}",
env=virtualenv_env,
)
# test the wheel
if build_options.test_requires:
call("pip", "install", *build_options.test_requires, env=virtualenv_env)
# run the tests from a temp dir, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
# and not the repo code)
test_command_prepared = prepare_command(
build_options.test_command,
project=Path(".").resolve(),
package=build_options.package_dir.resolve(),
)
test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd.mkdir(exist_ok=True)
(test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
# we're all done here; move it to output (overwrite existing)
if compatible_wheel is None:
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
finally:
pass
+26 -18
View File
@@ -120,19 +120,22 @@ python_configurations = [
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.3/python-3.12.3-macos11.pkg" },
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.3/python-3.12.3-macos11.pkg" },
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.3/python-3.12.3-macos11.pkg" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b1-macos11.pkg" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b1-macos11.pkg" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b1-macos11.pkg" },
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" },
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" },
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" },
{ identifier = "pp37-macosx_x86_64", version = "3.7", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2" },
{ identifier = "pp38-macosx_x86_64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2" },
{ identifier = "pp38-macosx_arm64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2" },
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2" },
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-macos_x86_64.tar.bz2" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-macos_arm64.tar.bz2" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_x86_64.tar.bz2" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_arm64.tar.bz2" },
]
[windows]
@@ -149,20 +152,25 @@ python_configurations = [
{ identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" },
{ identifier = "cp311-win32", version = "3.11.9", arch = "32" },
{ identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" },
{ identifier = "cp312-win32", version = "3.12.3", arch = "32" },
{ identifier = "cp312-win_amd64", version = "3.12.3", arch = "64" },
{ identifier = "cp313-win32", version = "3.13.0-b1", arch = "32" },
{ identifier = "cp313t-win32", version = "3.13.0-b1", arch = "32" },
{ identifier = "cp313-win_amd64", version = "3.13.0-b1", arch = "64" },
{ identifier = "cp313t-win_amd64", version = "3.13.0-b1", arch = "64" },
{ identifier = "cp312-win32", version = "3.12.6", arch = "32" },
{ identifier = "cp312-win_amd64", version = "3.12.6", arch = "64" },
{ identifier = "cp313-win32", version = "3.13.0-rc2", arch = "32" },
{ identifier = "cp313t-win32", version = "3.13.0-rc2", arch = "32" },
{ identifier = "cp313-win_amd64", version = "3.13.0-rc2", arch = "64" },
{ identifier = "cp313t-win_amd64", version = "3.13.0-rc2", arch = "64" },
{ identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" },
{ identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" },
{ identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" },
{ identifier = "cp312-win_arm64", version = "3.12.3", arch = "ARM64" },
{ identifier = "cp313-win_arm64", version = "3.13.0-b1", arch = "ARM64" },
{ identifier = "cp313t-win_arm64", version = "3.13.0-b1", arch = "ARM64" },
{ identifier = "cp312-win_arm64", version = "3.12.6", arch = "ARM64" },
{ identifier = "cp313-win_arm64", version = "3.13.0-rc2", arch = "ARM64" },
{ identifier = "cp313t-win_arm64", version = "3.13.0-rc2", arch = "ARM64" },
{ identifier = "pp37-win_amd64", version = "3.7", arch = "64", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip" },
{ identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
{ identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
{ identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-win64.zip" },
{ identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-win64.zip" },
]
[pyodide]
python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12.1", pyodide_version = "0.26.1", emscripten_version = "3.1.58", node_version = "v20" },
]
@@ -96,12 +96,13 @@
},
"build-frontend": {
"default": "default",
"description": "Set the tool to use to build, either \"pip\" (default for now) or \"build\"",
"description": "Set the tool to use to build, either \"pip\" (default for now), \"build\", or \"build[uv]\"",
"oneOf": [
{
"enum": [
"pip",
"build",
"build[uv]",
"default"
]
},
@@ -113,6 +114,10 @@
"type": "string",
"pattern": "^build; ?args:"
},
{
"type": "string",
"pattern": "^build\\[uv\\]; ?args:"
},
{
"type": "object",
"additionalProperties": false,
@@ -123,7 +128,8 @@
"name": {
"enum": [
"pip",
"build"
"build",
"build[uv]"
]
},
"args": {
@@ -765,6 +771,51 @@
"$ref": "#/properties/test-requires"
}
}
},
"pyodide": {
"type": "object",
"additionalProperties": false,
"properties": {
"archs": {
"$ref": "#/properties/archs"
},
"before-all": {
"$ref": "#/properties/before-all"
},
"before-build": {
"$ref": "#/properties/before-build"
},
"before-test": {
"$ref": "#/properties/before-test"
},
"build-frontend": {
"$ref": "#/properties/build-frontend"
},
"build-verbosity": {
"$ref": "#/properties/build-verbosity"
},
"config-settings": {
"$ref": "#/properties/config-settings"
},
"dependency-versions": {
"$ref": "#/properties/dependency-versions"
},
"environment": {
"$ref": "#/properties/environment"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
"test-command": {
"$ref": "#/properties/test-command"
},
"test-extras": {
"$ref": "#/properties/test-extras"
},
"test-requires": {
"$ref": "#/properties/test-requires"
}
}
}
}
}
@@ -0,0 +1,125 @@
# This file was autogenerated by uv via the following command:
# nox -s update_constraints
annotated-types==0.7.0
# via pydantic
anyio==4.4.0
# via httpx
auditwheel-emscripten==0.0.16
# via pyodide-build
build==1.2.2
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2024.8.30
# via
# httpcore
# httpx
# requests
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via typer
cloudpickle==3.0.0
# via loky
cmake==3.30.3
# via pyodide-build
distlib==0.3.8
# via virtualenv
filelock==3.16.0
# via virtualenv
h11==0.14.0
# via httpcore
httpcore==1.0.5
# via httpx
httpx==0.27.2
# via unearth
idna==3.8
# via
# anyio
# httpx
# requests
leb128==1.0.8
# via auditwheel-emscripten
loky==3.4.1
# via pyodide-build
markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==24.1
# via
# auditwheel-emscripten
# build
# pyodide-build
# unearth
pip==24.2
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.3.2
# via virtualenv
pydantic==2.9.0
# via
# pyodide-build
# pyodide-lock
pydantic-core==2.23.2
# via pydantic
pygments==2.18.0
# via rich
pyodide-build==0.26.1
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.2.4
# via
# auditwheel-emscripten
# pyodide-build
pyodide-lock==0.1.0a6
# via pyodide-build
pyproject-hooks==1.1.0
# via build
pyyaml==6.0.2
# via pyodide-build
requests==2.32.3
# via pyodide-build
resolvelib==1.0.1
# via pyodide-build
rich==13.8.0
# via
# pyodide-build
# pyodide-cli
# typer
ruamel-yaml==0.18.6
# via pyodide-build
ruamel-yaml-clib==0.2.8
# via ruamel-yaml
shellingham==1.5.4
# via typer
sniffio==1.3.1
# via
# anyio
# httpx
typer==0.12.5
# via
# auditwheel-emscripten
# pyodide-build
# pyodide-cli
types-requests==2.32.0.20240907
# via pyodide-build
typing-extensions==4.12.2
# via
# pydantic
# pydantic-core
# typer
tzdata==2024.1
# via pydantic
unearth==0.17.2
# via pyodide-build
urllib3==2.2.2
# via
# requests
# types-requests
virtualenv==20.26.4
# via
# build
# pyodide-build
wheel==0.44.0
# via
# auditwheel-emscripten
# pyodide-build
@@ -2,33 +2,33 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
importlib-metadata==7.1.0
importlib-metadata==8.4.0
# via build
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
tomli==2.0.1
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
zipp==3.18.2
zipp==3.20.1
# via importlib-metadata
@@ -2,27 +2,27 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
@@ -2,27 +2,27 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
@@ -2,27 +2,27 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.1b1
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
@@ -4,7 +4,7 @@ altgraph==0.17.4
# via macholib
build==1.1.1
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
@@ -33,7 +33,7 @@ typing-extensions==4.7.1
# delocate
# importlib-metadata
# platformdirs
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
zipp==3.15.0
# via importlib-metadata
+10 -10
View File
@@ -2,33 +2,33 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
importlib-metadata==7.1.0
importlib-metadata==8.4.0
# via build
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
tomli==2.0.1
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
zipp==3.18.2
zipp==3.20.1
# via importlib-metadata
+10 -10
View File
@@ -2,33 +2,33 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
importlib-metadata==7.1.0
importlib-metadata==8.4.0
# via build
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
tomli==2.0.1
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
zipp==3.18.2
zipp==3.20.1
# via importlib-metadata
+1 -2
View File
@@ -1,5 +1,4 @@
pip>=24.1b1 ; python_version >= '3.13'
pip ; python_version < '3.13'
pip
build
delocate
virtualenv
+8 -8
View File
@@ -2,27 +2,27 @@
# nox -s update_constraints
altgraph==0.17.4
# via macholib
build==1.2.1
build==1.2.2
# via -r cibuildwheel/resources/constraints.in
delocate==0.11.0
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.14.0
filelock==3.16.0
# via virtualenv
macholib==1.16.3
# via delocate
packaging==24.0
packaging==24.1
# via
# build
# delocate
pip==24.0
pip==24.2
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.2.2
platformdirs==4.3.2
# via virtualenv
pyproject-hooks==1.1.0
# via build
typing-extensions==4.11.0
typing-extensions==4.12.2
# via delocate
virtualenv==20.26.2
virtualenv==20.26.4
# via -r cibuildwheel/resources/constraints.in
+2
View File
@@ -46,3 +46,5 @@ repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
[tool.cibuildwheel.windows]
[tool.cibuildwheel.pyodide]
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>org.python.Python.PythonTFramework-3.13</string>
</dict>
</array>
</plist>
+2
View File
@@ -0,0 +1,2 @@
url = "https://nodejs.org/dist/"
v20 = "v20.17.0"
+24 -24
View File
@@ -1,54 +1,54 @@
[x86_64]
manylinux1 = quay.io/pypa/manylinux1_x86_64:2024-04-29-76807b8
manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024-05-13-0983f6f
musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024-05-13-0983f6f
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.09.09-0
musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.09.09-0
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.09.09-0
[i686]
manylinux1 = quay.io/pypa/manylinux1_i686:2024-04-29-76807b8
manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024-05-13-0983f6f
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024-05-13-0983f6f
musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.09.09-0
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.09.09-0
[pypy_x86_64]
manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.09.09-0
[pypy_i686]
manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024-05-13-0983f6f
musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024-05-13-0983f6f
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.09.09-0
musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.09.09-0
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.09.09-0
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024-05-13-0983f6f
musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024-05-13-0983f6f
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024.09.09-0
musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.09.09-0
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.09.09-0
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024-05-13-0983f6f
musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024-05-13-0983f6f
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024.09.09-0
musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.09.09-0
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.09.09-0
[pypy_aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024-05-13-0983f6f
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.09-0
manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024-05-13-0983f6f
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.09.09-0
+1 -1
View File
@@ -1,2 +1,2 @@
py36 = { version = "20.21.1", url = "https://github.com/pypa/get-virtualenv/blob/20.21.1/public/virtualenv.pyz?raw=true" }
default = { version = "20.26.2", url = "https://github.com/pypa/get-virtualenv/blob/20.26.2/public/virtualenv.pyz?raw=true" }
default = { version = "20.26.4", url = "https://github.com/pypa/get-virtualenv/blob/20.26.4/public/virtualenv.pyz?raw=true" }
+2 -2
View File
@@ -22,8 +22,8 @@ else:
PathOrStr = Union[str, "os.PathLike[str]"]
PlatformName = Literal["linux", "macos", "windows"]
PLATFORMS: Final[set[PlatformName]] = {"linux", "macos", "windows"}
PlatformName = Literal["linux", "macos", "windows", "pyodide"]
PLATFORMS: Final[set[PlatformName]] = {"linux", "macos", "windows", "pyodide"}
class GenericPythonConfiguration(Protocol):
+202 -85
View File
@@ -6,21 +6,25 @@ import itertools
import os
import re
import shlex
import shutil
import ssl
import subprocess
import sys
import tarfile
import textwrap
import time
import typing
import urllib.request
from collections import defaultdict
from collections.abc import Generator, Iterable, Mapping, Sequence
from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
from dataclasses import dataclass
from enum import Enum
from functools import cached_property, lru_cache
from functools import lru_cache
from pathlib import Path, PurePath
from tempfile import TemporaryDirectory
from time import sleep
from typing import Any, ClassVar, Final, Literal, TextIO, TypeVar
from zipfile import ZipFile
import bracex
import certifi
@@ -32,29 +36,33 @@ from packaging.version import Version
from platformdirs import user_cache_path
from ._compat import tomllib
from .architecture import Architecture
from .typing import PathOrStr, PlatformName
__all__ = [
"resources_dir",
"MANYLINUX_ARCHS",
"call",
"shell",
"find_compatible_wheel",
"format_safe",
"prepare_command",
"get_build_verbosity_extra_flags",
"read_python_configs",
"selector_matches",
"strtobool",
"cached_property",
"chdir",
"combine_constraints",
"find_compatible_wheel",
"find_uv",
"format_safe",
"get_build_verbosity_extra_flags",
"prepare_command",
"read_python_configs",
"resources_dir",
"selector_matches",
"shell",
"split_config_settings",
"strtobool",
]
resources_dir: Final[Path] = Path(__file__).parent / "resources"
install_certifi_script: Final[Path] = resources_dir / "install_certifi.py"
free_thread_enable_313: Final[Path] = resources_dir / "free-threaded-enable-313.xml"
test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py"
@@ -195,7 +203,9 @@ def get_build_verbosity_extra_flags(level: int) -> list[str]:
return []
def split_config_settings(config_settings: str, frontend: Literal["pip", "build"]) -> list[str]:
def split_config_settings(
config_settings: str, frontend: Literal["pip", "build", "build[uv]"]
) -> list[str]:
config_settings_list = shlex.split(config_settings)
s = "s" if frontend == "pip" else ""
return [f"--config-setting{s}={setting}" for setting in config_settings_list]
@@ -238,7 +248,7 @@ class BuildSelector:
requires_python: SpecifierSet | None = None
# a pattern that skips prerelease versions, when include_prereleases is False.
PRERELEASE_SKIP: ClassVar[str] = "cp313-* cp313t-*"
PRERELEASE_SKIP: ClassVar[str] = ""
prerelease_pythons: bool = False
free_threaded_support: bool = False
@@ -334,6 +344,55 @@ def download(url: str, dest: Path) -> None:
sleep(3)
def extract_zip(zip_src: Path, dest: Path) -> None:
with ZipFile(zip_src) as zip_:
for zinfo in zip_.filelist:
zip_.extract(zinfo, dest)
# Set permissions to the same values as they were set in the archive
# We have to do this manually due to
# https://github.com/python/cpython/issues/59999
# But some files in the zipfile seem to have external_attr with 0
# permissions. In that case just use the default value???
permissions = (zinfo.external_attr >> 16) & 0o777
if permissions != 0:
dest.joinpath(zinfo.filename).chmod(permissions)
def extract_tar(tar_src: Path, dest: Path) -> None:
with tarfile.open(tar_src) as tar_:
tar_.extraction_filter = getattr(tarfile, "tar_filter", (lambda member, _: member))
tar_.extractall(dest)
def move_file(src_file: Path, dst_file: Path) -> Path:
"""Moves a file safely while avoiding potential semantic confusion:
1. `dst_file` must point to the target filename, not a directory
2. `dst_file` will be overwritten if it already exists
3. any missing parent directories will be created
Returns the fully resolved Path of the resulting file.
Raises:
NotADirectoryError: If any part of the intermediate path to `dst_file` is an existing file
IsADirectoryError: If `dst_file` points directly to an existing directory
"""
src_file = src_file.resolve(strict=True)
dst_file = dst_file.resolve()
if dst_file.is_dir():
msg = "dst_file must be a valid target filename, not an existing directory."
raise IsADirectoryError(msg)
dst_file.unlink(missing_ok=True)
dst_file.parent.mkdir(parents=True, exist_ok=True)
# using shutil.move() as Path.rename() is not guaranteed to work across filesystem boundaries
# explicit str() needed for Python 3.8
resulting_file = shutil.move(str(src_file), str(dst_file))
return Path(resulting_file).resolve(strict=True)
class DependencyConstraints:
def __init__(self, base_file_path: Path):
assert base_file_path.exists()
@@ -343,12 +402,14 @@ class DependencyConstraints:
def with_defaults() -> DependencyConstraints:
return DependencyConstraints(base_file_path=resources_dir / "constraints.txt")
def get_for_python_version(self, version: str) -> Path:
def get_for_python_version(
self, version: str, *, variant: Literal["python", "pyodide"] = "python"
) -> Path:
version_parts = version.split(".")
# try to find a version-specific dependency file e.g. if
# ./constraints.txt is the base, look for ./constraints-python36.txt
specific_stem = self.base_file_path.stem + f"-python{version_parts[0]}{version_parts[1]}"
specific_stem = self.base_file_path.stem + f"-{variant}{version_parts[0]}{version_parts[1]}"
specific_name = specific_stem + self.base_file_path.suffix
specific_file_path = self.base_file_path.with_name(specific_name)
@@ -373,7 +434,7 @@ class DependencyConstraints:
return self.base_file_path.name
BuildFrontendName = Literal["pip", "build"]
BuildFrontendName = Literal["pip", "build", "build[uv]"]
@dataclass(frozen=True)
@@ -385,8 +446,8 @@ class BuildFrontendConfig:
def from_config_string(config_string: str) -> BuildFrontendConfig:
config_dict = parse_key_value_string(config_string, ["name"], ["args"])
name = " ".join(config_dict["name"])
if name not in {"pip", "build"}:
msg = f"Unrecognised build frontend {name}, only 'pip' and 'build' are supported"
if name not in {"pip", "build", "build[uv]"}:
msg = f"Unrecognised build frontend {name!r}, only 'pip', 'build', and 'build[uv]' are supported"
raise ValueError(msg)
name = typing.cast(BuildFrontendName, name)
@@ -401,37 +462,6 @@ class BuildFrontendConfig:
return {"name": self.name, "args": repr(self.args)}
class NonPlatformWheelError(Exception):
def __init__(self) -> None:
message = textwrap.dedent(
"""
cibuildwheel: Build failed because a pure Python wheel was generated.
If you intend to build a pure-Python wheel, you don't need cibuildwheel - use
`pip wheel -w DEST_DIR .` instead.
If you expected a platform wheel, check your project configuration, or run
cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
"""
)
super().__init__(message)
class AlreadyBuiltWheelError(Exception):
def __init__(self, wheel_name: str) -> None:
message = textwrap.dedent(
f"""
cibuildwheel: Build failed because a wheel named {wheel_name} was already generated in the current run.
If you expected another wheel to be generated, check your project configuration, or run
cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
"""
)
super().__init__(message)
def strtobool(val: str) -> bool:
return val.lower() in {"y", "yes", "t", "true", "on", "1"}
@@ -534,6 +564,38 @@ def get_pip_version(env: Mapping[str, str]) -> str:
return pip_version
@lru_cache(maxsize=None)
def ensure_node(major_version: str) -> Path:
input_file = resources_dir / "nodejs.toml"
with input_file.open("rb") as f:
loaded_file = tomllib.load(f)
version = str(loaded_file[major_version])
base_url = str(loaded_file["url"])
ext = "zip" if IS_WIN else "tar.xz"
platform = "win" if IS_WIN else ("darwin" if sys.platform.startswith("darwin") else "linux")
linux_arch = Architecture.native_arch("linux")
assert linux_arch is not None
arch = {"x86_64": "x64", "i686": "x86", "aarch64": "arm64"}.get(
linux_arch.value, linux_arch.value
)
name = f"node-{version}-{platform}-{arch}"
path = CIBW_CACHE_PATH / name
with FileLock(str(path) + ".lock"):
if not path.exists():
url = f"{base_url}{version}/{name}.{ext}"
with TemporaryDirectory() as tmp_path:
archive = Path(tmp_path) / f"{name}.{ext}"
download(url, archive)
if ext == "zip":
extract_zip(archive, path.parent)
else:
extract_tar(archive, path.parent)
assert path.exists()
if not IS_WIN:
return path / "bin"
return path
@lru_cache(maxsize=None)
def _ensure_virtualenv(version: str) -> Path:
version_parts = version.split(".")
@@ -599,48 +661,64 @@ def _parse_constraints_for_virtualenv(
def virtualenv(
version: str, python: Path, venv_path: Path, dependency_constraint_flags: Sequence[PathOrStr]
version: str,
python: Path,
venv_path: Path,
dependency_constraint_flags: Sequence[PathOrStr],
*,
use_uv: bool,
) -> dict[str, str]:
"""
Create a virtual environment. If `use_uv` is True,
dependency_constraint_flags are ignored since nothing is installed in the
venv. Otherwise, pip is installed, and setuptools + wheel if Python < 3.12.
"""
assert python.exists()
virtualenv_app = _ensure_virtualenv(version)
allowed_seed_packages = ["pip", "setuptools", "wheel"]
constraints = _parse_constraints_for_virtualenv(
allowed_seed_packages, dependency_constraint_flags
)
additional_flags: list[str] = []
for package in allowed_seed_packages:
if package in constraints:
additional_flags.append(f"--{package}={constraints[package]}")
else:
additional_flags.append(f"--no-{package}")
# Using symlinks to pre-installed seed packages is really the fastest way to get a virtual
# environment. The initial cost is a bit higher but reusing is much faster.
# Windows does not always allow symlinks so just disabling for now.
# Requires pip>=19.3 so disabling for "embed" because this means we don't know what's the
# version of pip that will end-up installed.
# c.f. https://virtualenv.pypa.io/en/latest/cli_interface.html#section-seeder
if (
not IS_WIN
and constraints["pip"] != "embed"
and Version(constraints["pip"]) >= Version("19.3")
):
additional_flags.append("--symlink-app-data")
if use_uv:
call("uv", "venv", venv_path, "--python", python)
else:
virtualenv_app = _ensure_virtualenv(version)
allowed_seed_packages = ["pip", "setuptools", "wheel"]
constraints = _parse_constraints_for_virtualenv(
allowed_seed_packages, dependency_constraint_flags
)
additional_flags: list[str] = []
for package in allowed_seed_packages:
if package in constraints:
additional_flags.append(f"--{package}={constraints[package]}")
else:
additional_flags.append(f"--no-{package}")
# Using symlinks to pre-installed seed packages is really the fastest way to get a virtual
# environment. The initial cost is a bit higher but reusing is much faster.
# Windows does not always allow symlinks so just disabling for now.
# Requires pip>=19.3 so disabling for "embed" because this means we don't know what's the
# version of pip that will end-up installed.
# c.f. https://virtualenv.pypa.io/en/latest/cli_interface.html#section-seeder
if (
not IS_WIN
and constraints["pip"] != "embed"
and Version(constraints["pip"]) >= Version("19.3")
):
additional_flags.append("--symlink-app-data")
call(
sys.executable,
"-sS", # just the stdlib, https://github.com/pypa/virtualenv/issues/2133#issuecomment-1003710125
virtualenv_app,
"--activators=",
"--no-periodic-update",
*additional_flags,
"--python",
python,
venv_path,
)
call(
sys.executable,
"-sS", # just the stdlib, https://github.com/pypa/virtualenv/issues/2133#issuecomment-1003710125
virtualenv_app,
"--activators=",
"--no-periodic-update",
*additional_flags,
"--python",
python,
venv_path,
)
paths = [str(venv_path), str(venv_path / "Scripts")] if IS_WIN else [str(venv_path / "bin")]
env = os.environ.copy()
env["PATH"] = os.pathsep.join([*paths, env["PATH"]])
env["VIRTUAL_ENV"] = str(venv_path)
return env
@@ -782,3 +860,42 @@ def parse_key_value_string(
result[field_name] += values
return dict(result)
def find_uv() -> Path | None:
# Prefer uv in our environment
with contextlib.suppress(ImportError, FileNotFoundError):
# pylint: disable-next=import-outside-toplevel
from uv import find_uv_bin
return Path(find_uv_bin())
uv_on_path = shutil.which("uv")
return Path(uv_on_path) if uv_on_path else None
def combine_constraints(
env: MutableMapping[str, str], /, constraints_path: Path, tmp_dir: Path | None
) -> None:
"""
This will workaround a bug in pip<=21.1.1 or uv<=0.2.0 if a tmp_dir is given.
If set to None, this will use the modern URI method.
"""
if tmp_dir:
if " " in str(constraints_path):
assert " " not in str(tmp_dir)
tmp_file = tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
our_constraints = str(constraints_path)
else:
our_constraints = (
constraints_path.as_uri() if " " in str(constraints_path) else str(constraints_path)
)
user_constraints = env.get("PIP_CONSTRAINT")
env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join(
c for c in [our_constraints, user_constraints] if c
)
+95 -88
View File
@@ -4,18 +4,16 @@ import os
import platform as platform_module
import shutil
import subprocess
import sys
import textwrap
from collections.abc import MutableMapping, Sequence, Set
from contextlib import suppress
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from zipfile import ZipFile
from filelock import FileLock
from packaging.version import Version
from . import errors
from ._compat.typing import assert_never
from .architecture import Architecture
from .environment import ParsedEnvironment
@@ -24,16 +22,18 @@ from .options import Options
from .typing import PathOrStr
from .util import (
CIBW_CACHE_PATH,
AlreadyBuiltWheelError,
BuildFrontendConfig,
BuildFrontendName,
BuildSelector,
NonPlatformWheelError,
call,
combine_constraints,
download,
extract_zip,
find_compatible_wheel,
find_uv,
get_build_verbosity_extra_flags,
get_pip_version,
move_file,
prepare_command,
read_python_configs,
shell,
@@ -96,11 +96,6 @@ def get_python_configurations(
return python_configurations
def extract_zip(zip_src: Path, dest: Path) -> None:
with ZipFile(zip_src) as zip_:
zip_.extractall(dest)
@lru_cache(maxsize=None)
def _ensure_nuget() -> Path:
nuget = CIBW_CACHE_PATH / "nuget.exe"
@@ -157,6 +152,16 @@ def setup_setuptools_cross_compile(
# set the platform name
map_plat = {"32": "win32", "64": "win-amd64", "ARM64": "win-arm64"}
plat_name = map_plat[python_configuration.arch]
# Set environment variable so that setuptools._distutils.get_platform()
# identifies the target, not the host
vscmd_arg_tgt_arch = {"32": "x86", "64": "x64", "ARM64": "arm64"}
current_tgt_arch = vscmd_arg_tgt_arch[python_configuration.arch]
if (env.get("VSCMD_ARG_TGT_ARCH") or current_tgt_arch) != current_tgt_arch:
msg = f"VSCMD_ARG_TGT_ARCH must be set to {current_tgt_arch!r}, got {env['VSCMD_ARG_TGT_ARCH']!r}. If you're setting up MSVC yourself (e.g. using vcvarsall.bat or msvc-dev-cmd), make sure to target the right architecture. Alternatively, run cibuildwheel without configuring MSVC, and let the build backend handle it."
raise errors.FatalError(msg)
env["VSCMD_ARG_TGT_ARCH"] = current_tgt_arch
# (This file must be default/locale encoding, so we can't pass 'encoding')
distutils_cfg.write_text(
textwrap.dedent(
@@ -226,7 +231,7 @@ def setup_python(
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
build_frontend: BuildFrontendName,
) -> dict[str, str]:
) -> tuple[Path, dict[str, str]]:
tmp.mkdir()
implementation_id = python_configuration.identifier.split("-")[0]
python_libs_base = None
@@ -249,10 +254,21 @@ def setup_python(
raise ValueError(msg)
assert base_python.exists()
use_uv = (
build_frontend == "build[uv]"
and Version(python_configuration.version) >= Version("3.8")
and not python_configuration.identifier.startswith("pp38-")
)
uv_path = find_uv()
log.step("Setting up build environment...")
venv_path = tmp / "venv"
env = virtualenv(
python_configuration.version, base_python, venv_path, dependency_constraint_flags
python_configuration.version,
base_python,
venv_path,
dependency_constraint_flags,
use_uv=use_uv,
)
# set up environment variables for run_with_env
@@ -260,19 +276,14 @@ def setup_python(
env["PYTHON_ARCH"] = python_configuration.arch
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# pip older than 21.3 builds executables such as pip.exe for x64 platform.
# The first re-install of pip updates pip module but builds pip.exe using
# the old pip which still generates x64 executable. But the second
# re-install uses updated pip and correctly builds pip.exe for the target.
# This can be removed once ARM64 Pythons (currently 3.9 and 3.10) bundle
# pip versions newer than 21.3.
if python_configuration.arch == "ARM64" and Version(get_pip_version(env)) < Version("21.3"):
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip.exe'
if not use_uv:
call(
"python",
"-m",
"pip",
"install",
"--force-reinstall",
"--upgrade",
"pip",
*dependency_constraint_flags,
@@ -280,20 +291,6 @@ def setup_python(
cwd=venv_path,
)
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip.exe'
call(
"python",
"-m",
"pip",
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
# update env with results from CIBW_ENVIRONMENT
env = environment.as_dictionary(prev_environment=env)
@@ -303,23 +300,18 @@ def setup_python(
call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env)
where_python = call("where", "python", env=env, capture_stdout=True).splitlines()[0].strip()
if where_python != str(venv_path / "Scripts" / "python.exe"):
print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
file=sys.stderr,
)
sys.exit(1)
msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg)
# check what pip version we're on
assert (venv_path / "Scripts" / "pip.exe").exists()
where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip()
if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"):
print(
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.",
file=sys.stderr,
)
sys.exit(1)
if not use_uv:
assert (venv_path / "Scripts" / "pip.exe").exists()
where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip()
if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"):
msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
call("pip", "--version", env=env)
call("pip", "--version", env=env)
log.step("Installing build tools...")
if build_frontend == "build":
@@ -331,13 +323,24 @@ def setup_python(
*dependency_constraint_flags,
env=env,
)
elif build_frontend == "build[uv]":
assert uv_path is not None
call(
uv_path,
"pip",
"install",
"--upgrade",
"build[virtualenv]",
*dependency_constraint_flags,
env=env,
)
if python_libs_base:
# Set up the environment for various backends to enable cross-compilation
setup_setuptools_cross_compile(tmp, python_configuration, python_libs_base, env)
setup_rust_cross_compile(tmp, python_configuration, python_libs_base, env)
return env
return base_python, env
def build(options: Options, tmp_path: Path) -> None:
@@ -365,6 +368,11 @@ def build(options: Options, tmp_path: Path) -> None:
for config in python_configurations:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = (
build_frontend.name == "build[uv]"
and Version(config.version) >= Version("3.8")
and not config.identifier.startswith("pp38-")
)
log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier
@@ -380,13 +388,15 @@ def build(options: Options, tmp_path: Path) -> None:
]
# install Python
env = setup_python(
base_python, env = setup_python(
identifier_tmp_dir / "build",
config,
dependency_constraint_flags,
build_options.environment,
build_frontend.name,
)
if not use_uv:
pip_version = get_pip_version(env)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
@@ -415,26 +425,14 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags += build_frontend.args
build_env = env.copy()
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
if not use_uv:
build_env["VIRTUALENV_PIP"] = pip_version
if build_options.dependency_constraints:
constraints_path = build_options.dependency_constraints.get_for_python_version(
config.version
)
# Bug in pip <= 21.1.3 - we can't have a space in the
# constraints file, and pip doesn't support drive letters
# in uhi. After probably pip 21.2, we can use uri. For
# now, use a temporary file.
if " " in str(constraints_path):
assert " " not in str(identifier_tmp_dir)
tmp_file = identifier_tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
our_constraints = str(constraints_path)
user_constraints = build_env.get("PIP_CONSTRAINT")
build_env["PIP_CONSTRAINT"] = " ".join(
c for c in [our_constraints, user_constraints] if c
)
combine_constraints(build_env, constraints_path, identifier_tmp_dir)
if build_frontend.name == "pip":
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
@@ -451,10 +449,12 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=build_env,
)
elif build_frontend.name == "build":
elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags.append("--installer=uv")
call(
"python",
"-m",
@@ -474,7 +474,7 @@ def build(options: Options, tmp_path: Path) -> None:
repaired_wheel_dir.mkdir()
if built_wheel.name.endswith("none-any.whl"):
raise NonPlatformWheelError()
raise errors.NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
@@ -490,7 +490,7 @@ def build(options: Options, tmp_path: Path) -> None:
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise AlreadyBuiltWheelError(repaired_wheel.name)
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
test_selected = options.globals.test_selector(config.identifier)
if test_selected and config.arch == "ARM64" != platform_module.machine():
@@ -508,12 +508,20 @@ def build(options: Options, tmp_path: Path) -> None:
log.step("Testing wheel...")
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
if not use_uv:
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
venv_dir = identifier_tmp_dir / "venv-test"
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
call("python", "-m", "virtualenv", "--no-download", venv_dir, env=env)
if use_uv:
call("uv", "venv", venv_dir, f"--python={base_python}", env=env)
else:
# Use pip version from the initial env to ensure determinism
venv_args = ["--no-periodic-update", f"--pip={pip_version}"]
# In Python<3.12, setuptools & wheel are installed as well, use virtualenv embedded ones
if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
call("python", "-m", "virtualenv", *venv_args, venv_dir, env=env)
virtualenv_env = env.copy()
virtualenv_env["PATH"] = os.pathsep.join(
@@ -522,14 +530,11 @@ def build(options: Options, tmp_path: Path) -> None:
virtualenv_env["PATH"],
]
)
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
# check that we are using the Python from the virtual environment
call("where", "python", env=virtualenv_env)
# TODO remove me once virtualenv provides pip>=24.1b1
if config.version.startswith("3.13."):
call("python", "-m", "pip", "install", "--pre", "-U", "pip", env=virtualenv_env)
if build_options.before_test:
before_test_prepared = prepare_command(
build_options.before_test,
@@ -538,9 +543,11 @@ def build(options: Options, tmp_path: Path) -> None:
)
shell(before_test_prepared, env=virtualenv_env)
pip = ["uv", "pip"] if use_uv else ["pip"]
# install the wheel
call(
"pip",
*pip,
"install",
str(repaired_wheel) + build_options.test_extras,
env=virtualenv_env,
@@ -548,7 +555,7 @@ def build(options: Options, tmp_path: Path) -> None:
# test the wheel
if build_options.test_requires:
call("pip", "install", *build_options.test_requires, env=virtualenv_env)
call(*pip, "install", *build_options.test_requires, env=virtualenv_env)
# run the tests from a temp dir, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
@@ -567,11 +574,13 @@ def build(options: Options, tmp_path: Path) -> None:
# we're all done here; move it to output (remove if already exists)
if compatible_wheel is None:
with suppress(FileNotFoundError):
(build_options.output_dir / repaired_wheel.name).unlink()
shutil.move(str(repaired_wheel), build_options.output_dir)
built_wheels.append(build_options.output_dir / repaired_wheel.name)
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
# clean up
# (we ignore errors because occasionally Windows fails to unlink a file and we
@@ -580,7 +589,5 @@ def build(options: Options, tmp_path: Path) -> None:
log.build_end()
except subprocess.CalledProcessError as error:
log.step_end_with_error(
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
)
sys.exit(1)
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
raise errors.FatalError(msg) from error
+71
View File
@@ -4,8 +4,79 @@ title: Changelog
# Changelog
### v2.21.0
_13 September 2024_
- ⚠️ Update CPython 3.12 to 3.12.6, which changes the macOS minimum deployment target on CPython 3.12 from macOS 10.9 to macOS 10.13 (#1998)
- 🛠 Changes the behaviour when inheriting `config-settings` in TOML overrides - rather than extending each key, which is rarely useful, individual keys will override previously set values. (#1803)
- 🛠 Update CPython 3.13 to 3.13.0rc2 (#1998)
- ✨ Adds support for multiarch OCI images (#1961)
- 🐛 Fixes some bugs building Linux wheels on macOS. (#1961)
- ⚠️ Changes the minimum version of Docker/Podman to Docker API version 1.43, Podman API version 3. The only mainstream runner this should affect is Travis Graviton2 runners - if so you can [upgrade your version of Docker](https://github.com/pypa/cibuildwheel/pull/1961#issuecomment-2304060019). (#1961)
### v2.20.0
_4 August 2024_
- 🌟 CPython 3.13 wheels are now built by default - without the `CIBW_PRERELEASE_PYTHONS` flag. It's time to build and upload these wheels to PyPI! This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release. Free-threading is still behind a flag/config option. (#1950)
- ✨ Provide a `CIBW_ALLOW_EMPTY` environment variable as an alternative to the command line flag. (#1937)
- 🐛 Don't use uv on PyPy3.8 on Windows, it stopped working starting in 0.2.25. Note that PyPy 3.8 is EoL. (#1868)
- 🛠 Set the `VSCMD_ARG_TGT_ARCH` variable based on target arch. (#1876)
- 🛠 Undo cleaner output on pytest 8-8.2 now that 8.3 is out. (#1943)
- 📚 Update examples to use Python 3.12 on host (cibuildwheel will require Python 3.11+ on the host machine starting in October 2024) (#1919)
### v2.19.2
_2 July 2024_
- 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917)
- 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889)
- 🛠 Provide attestations for releases at <https://github.com/pypa/cibuildwheel/attestations>. (#1916)
- 🛠 Provide CPython 3.13.0b3. (#1913)
- 🛠 Remove some workarounds now that pip 21.1 is available. (#1891, #1892)
- 📚 Remove nosetest from our docs. (#1821)
- 📚 Document the macOS ARM workaround for 3.8 on GHA. (#1871)
- 📚 GitLab CI + macOS is now a supported platform with an example. (#1911)
### v2.19.1
_13 June 2024_
- 🐛 Don't require setup-python on GHA for Pyodide (#1868)
- 🐛 Specify full python path for uv (fixes issue in 0.2.10 & 0.2.11) (#1881)
- 🛠 Update for pip 24.1b2 on CPython 3.13. (#1879)
- 🛠 Fix a warning in our schema generation script. (#1866)
- 🛠 Cleaner output on pytest 8-8.2. (#1865)
### v2.19.0
_10 June 2024_
See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more info on new features!
- 🌟 Add Pyodide platform. Set with `--platform pyodide` or `CIBW_PLATFORM: pyodide` on Linux with a host Python 3.12 to build WebAssembly wheels. Not accepted on PyPI currently, but usable directly in a website using Pyodide, for live docs, etc. (#1456, #1859)
- 🌟 Add `build[uv]` backend, which will take a pre-existing uv install (or install `cibuildwheel[uv]`) and use `uv` for all environment setup and installs on Python 3.8+. This is significantly faster in most cases. (#1856)
- ✨ Add free-threaded macOS builds and update CPython to 3.13.0b2. (#1854)
- 🐛 Issue copying a wheel to a non-existent output dir fixed. (#1851, #1862)
- 🐛 Better determinism for the test environment seeding. (#1835)
- 🛠 `VIRTUAL_ENV` variable now set. (#1842)
- 🛠 Remove a pip<21.3 workaround. (#1842)
- 🛠 Error handling was refactored to use exceptions. (#1719)
- 🛠 Hardcoded paths in tests avoided. (#1834)
- 🛠 Single Python tests made more generic. (#1835)
- 🛠 Sped up our ci by splitting up emulation tests. (#1839)
### v2.18.1
_20 May 2024_
- 🌟 Add free-threaded Linux and Windows builds for 3.13. New identifiers `cp313t-*`, new option `CIBW_FREE_THREADED_SUPPORT`/`tool.cibuildwheel.free-threaded-support` required to opt-in. [See the docs](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support) for more information. (#1831)
- ✨ The `container-engine` is now a build (non-global) option. (#1792)
- 🛠 The build backend for cibuildwheel is now hatchling. (#1297)
+4 -2
View File
@@ -109,7 +109,7 @@ You can see a list of sessions by typing `nox -l`; here are a few common ones:
```console
nox -s lint # Run the linters (default)
nox -s tests [-- PYTEST-ARGS] # Run the tests (default)
nox -s docs -- serve # Build and serve the documentation
nox -s docs # Build and serve the documentation
nox -s build # Make SDist and wheel
```
@@ -177,7 +177,7 @@ Then, increment the project version number using:
bin/bump_version.py
```
You'll be prompted to enter the new version number. Update the changelog when prompted. The script will create a 'bump version' commit and version tag.
(or `nox -s bump_version`) You'll be prompted to enter the new version number. Update the changelog when prompted. The script will create a 'bump version' commit and version tag.
Finally, cut the release and push to GitHub.
@@ -186,3 +186,5 @@ git push && git push --tags
```
Then head to https://github.com/pypa/cibuildwheel/releases and create a GitHub release from the new tag, pasting in the changelog entry. Once the release is created inside GitHub, a CI job will create the assets and upload them to PyPI.
If there were any schema updates, run `pipx run ./bin/generate_schema.py --schemastore > partial-cibuildwheel.json` and contribute the changes to SchemaStore.
+7
View File
@@ -657,3 +657,10 @@
C++ framework. Uses cibuildwheel to deploy on as many operating
systems and Python versions as possible with only one dependency
(any NumPy).
- name: streaming-form-data
gh: siddhantgoel/streaming-form-data
ci: [github]
os: [apple, linux, windows]
pypi: streaming-form-data
notes: Streaming parser for multipart/form-data written in Cython
+20 -11
View File
@@ -142,7 +142,7 @@ There are two suggested methods for keeping cibuildwheel up to date that instead
If you use GitHub Actions for builds, you can use cibuildwheel as an action:
```yaml
uses: pypa/cibuildwheel@v2.18.1
uses: pypa/cibuildwheel@v2.21.0
```
This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal.
@@ -164,7 +164,7 @@ The second option, and the only one that supports other CI systems, is using a `
```bash
# requirements-cibw.txt
cibuildwheel==2.18.1
cibuildwheel==2.21.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:
@@ -328,7 +328,7 @@ Solutions to this vary, but the simplest is to use pipx:
# most runners have pipx preinstalled, but in case you don't
python3 -m pip install pipx
pipx run cibuildwheel==2.18.1 --output-dir wheelhouse
pipx run cibuildwheel==2.21.0 --output-dir wheelhouse
pipx run twine upload wheelhouse/*.whl
```
@@ -363,16 +363,25 @@ If you're building on an arm64 runner, you might notice something strange about
This is fine for simple C extensions, but for more complicated builds on arm64 it becomes an issue.
So, if the cross-compilation is an issue for you, there is an 'experimental' installer available that's built natively for arm64.
To use this installer and perform native CPython 3.8 building, before invoking cibuildwheel, install the universal2 version of Python on your arm64 runner, something like:
So, if you want to build macOS arm64 wheels on an arm64 runner (e.g., `macos-14`) on Python 3.8, before invoking cibuildwheel, you should install a native arm64 Python 3.8 interpreter on the runner:
```bash
curl -o /tmp/Python38.pkg https://www.python.org/ftp/python/3.8.10/python-3.8.10-macos11.pkg
sudo installer -pkg /tmp/Python38.pkg -target /
sh "/Applications/Python 3.8/Install Certificates.command"
```
!!! tab "GitHub Actions"
```yaml
- uses: actions/setup-python@v5
with:
python-version: 3.8
if: runner.os == 'macOS' && runner.arch == 'ARM64'
```
!!! tab "Generic"
```bash
curl -o /tmp/Python38.pkg https://www.python.org/ftp/python/3.8.10/python-3.8.10-macos11.pkg
sudo installer -pkg /tmp/Python38.pkg -target /
sh "/Applications/Python 3.8/Install Certificates.command"
```
Then cibuildwheel will detect that it's installed and use it instead. However, you probably don't want to build x86_64 wheels on this Python, unless you're happy with them only supporting macOS 11+.
+114 -28
View File
@@ -91,9 +91,9 @@ 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][] practice. Anything placed in subsections `linux`, `windows`,
or `macos` 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
`macos`, or `pyodide` will only affect those platforms. Lists can be used
instead of strings for items that are naturally a list. Multiline strings also
work just like in the environment variables. Environment variables will take
precedence if defined.
The example above using environment variables could have been written like this:
@@ -247,7 +247,7 @@ environment variables will completely override any TOML configuration.
> Override the auto-detected target platform
Options: `auto` `linux` `macos` `windows`
Options: `auto` `linux` `macos` `windows` `pyodide`
Default: `auto`
@@ -255,6 +255,7 @@ Default: `auto`
- For `linux`, you need [Docker or Podman](#container-engine) running, on Linux, macOS, or Windows.
- For `macos` and `windows`, you need to be running on the respective system, with a working compiler toolchain installed - Xcode Command Line tools for macOS, and MSVC for Windows.
- For `pyodide` you need to be on an x86-64 linux runner and `python3.12` must be available in `PATH`.
This option can also be set using the [command-line option](#command-line) `--platform`. This option is not available in the `pyproject.toml` config.
@@ -309,6 +310,9 @@ For CPython, the minimally supported macOS version is 10.9; for PyPy 3.7, macOS
Windows arm64 platform support is experimental.
For an experimental WebAssembly build with `--platform pyodide`,
`cp312-pyodide_wasm32` is the only platform identifier.
See the [cibuildwheel 1 documentation](https://cibuildwheel.pypa.io/en/1.x/) for past end-of-life versions of Python, and PyPy2.7.
#### Examples
@@ -474,6 +478,7 @@ Options:
- Linux: `x86_64` `i686` `aarch64` `ppc64le` `s390x`
- macOS: `x86_64` `arm64` `universal2`
- Windows: `AMD64` `x86` `ARM64`
- Pyodide: `wasm32`
- `auto`: The default archs for your machine - see the table below.
- `auto64`: Just the 64-bit auto archs
- `auto32`: Just the 32-bit auto archs
@@ -616,10 +621,32 @@ This option can also be set using the [command-line option](#command-line) `--pr
CIBW_PRERELEASE_PYTHONS: True
```
### `CIBW_ALLOW_EMPTY` {: #allow-empty}
> Suppress the error code if no wheels match the specified build identifiers
When none of the specified build identifiers match any available versions,
cibuildwheel will typically return error code 3, indicating that there are
no wheels to build. Enabling this option will suppress this error, allowing
the build process to complete without signaling an error.
Default: Off (0). Error code 3 is returned when no builds are selected.
This option can also be set using the [command-line option](#command-line)
`--allow-empty`. This option is not available in the `pyproject.toml` config.
#### Examples
!!! tab examples "Environment variables"
```yaml
# Prevent an error code if the build does not match any wheels
CIBW_ALLOW_EMPTY: True
```
## Build customization
### `CIBW_BUILD_FRONTEND` {: #build-frontend}
> Set the tool to use to build, either "pip" (default for now) or "build"
> Set the tool to use to build, either "pip" (default for now), "build", or "build\[uv\]"
Options:
@@ -631,17 +658,36 @@ Default: `pip`
Choose which build frontend to use. Can either be "pip", which will run
`python -m pip wheel`, or "build", which will run `python -m build --wheel`.
You can also use "build\[uv\]", which will use an external [uv][] everywhere
possible, both through `--installer=uv` passed to build, as well as when making
all build and test environments. This will generally speed up cibuildwheel.
Make sure you have an external uv on Windows and macOS, either by
pre-installing it, or installing cibuildwheel with the uv extra,
`cibuildwheel[uv]`. `uv` will not be used for Python 3.6 or Python 3.7. You
cannot use uv currently on Windows for ARM or for musllinux on s390x as
binaries are not provided by uv. Legacy dependencies like setuptools on Python
< 3.12 and pip are not installed if using uv.
Pyodide ignores this setting, as only "build" is supported.
You can specify extra arguments to pass to `pip wheel` or `build` using the
optional `args` option.
!!! tip
Until v2.0.0, [pip] was the only way to build wheels, and is still the
Until v2.0.0, [pip][] was the only way to build wheels, and is still the
default. However, we expect that at some point in the future, cibuildwheel
will change the default to [build], in line with the PyPA's recommendation.
will change the default to [build][], in line with the PyPA's recommendation.
If you want to try `build` before this, you can use this option.
!!! warning
If you are using `build[uv]` and are passing `--no-isolation` or `-n`, we
will detect this and avoid passing `--installer=uv` to build, but still
install all packages with uv. We do not currently detect combined short
options, like `-xn`!
[pip]: https://pip.pypa.io/en/stable/cli/pip_wheel/
[build]: https://github.com/pypa/build/
[uv]: https://github.com/astral-sh/uv
#### Examples
@@ -656,6 +702,12 @@ optional `args` option.
# supply an extra argument to 'pip wheel'
CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation"
# Use uv and build
CIBW_BUILD_FRONTEND: "build[uv]"
# Use uv and build with an argument
CIBW_BUILD_FRONTEND: "build[uv]; args: --no-isolation"
```
!!! tab examples "pyproject.toml"
@@ -670,6 +722,12 @@ optional `args` option.
# supply an extra argument to 'pip wheel'
build-frontend = { name = "pip", args = ["--no-build-isolation"] }
# Use uv and build
build-frontend = "build[uv]"
# Use uv and build with an argument
build-frontend = { name = "build[uv]", args = ["--no-isolation"] }
```
### `CIBW_CONFIG_SETTINGS` {: #config-settings}
@@ -684,7 +742,7 @@ a table of items, including arrays.
single values.
Platform-specific environment variables also available:<br/>
`CIBW_CONFIG_SETTINGS_MACOS` | `CIBW_CONFIG_SETTINGS_WINDOWS` | `CIBW_CONFIG_SETTINGS_LINUX`
`CIBW_CONFIG_SETTINGS_MACOS` | `CIBW_CONFIG_SETTINGS_WINDOWS` | `CIBW_CONFIG_SETTINGS_LINUX` | `CIBW_CONFIG_SETTINGS_PYODIDE`
#### Examples
@@ -715,7 +773,7 @@ You can use `$PATH` syntax to insert other variables, or the `$(pwd)` syntax to
To specify more than one environment variable, separate the assignments by spaces.
Platform-specific environment variables are also available:<br/>
`CIBW_ENVIRONMENT_MACOS` | `CIBW_ENVIRONMENT_WINDOWS` | `CIBW_ENVIRONMENT_LINUX`
`CIBW_ENVIRONMENT_MACOS` | `CIBW_ENVIRONMENT_WINDOWS` | `CIBW_ENVIRONMENT_LINUX` | `CIBW_ENVIRONMENT_PYODIDE`
#### Examples
@@ -788,7 +846,7 @@ Platform-specific environment variables are also available:<br/>
In configuration mode, you can use a [TOML][] table instead of a raw string as shown above.
!!! note
cibuildwheel always defines the environment variable `CIBUILDWHEEL=1`. This can be useful for [building wheels with optional extensions](faq.md#building-packages-with-optional-c-extensions).
cibuildwheel always defines the environment variable `CIBUILDWHEEL=1`. This can be useful for [building wheels with optional extensions](faq.md#optional-extensions).
!!! note
To do its work, cibuildwheel sets the variables `VIRTUALENV_PIP`, `DIST_EXTRA_CONFIG`, `SETUPTOOLS_EXT_SUFFIX`, `PIP_DISABLE_PIP_VERSION_CHECK`, `PIP_ROOT_USER_ACTION`, and it extends the variables `PATH` and `PIP_CONSTRAINT`. Your assignments to these options might be replaced or extended.
@@ -847,7 +905,7 @@ On linux, overriding it triggers a new container launch. It cannot be overridden
on macOS and Windows.
Platform-specific environment variables also available:<br/>
`CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX`
`CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX` | `CIBW_BEFORE_ALL_PYODIDE`
!!! note
@@ -912,7 +970,7 @@ The active Python binary can be accessed using `python`, and pip with `pip`; cib
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
Platform-specific environment variables are also available:<br/>
`CIBW_BEFORE_BUILD_MACOS` | `CIBW_BEFORE_BUILD_WINDOWS` | `CIBW_BEFORE_BUILD_LINUX`
`CIBW_BEFORE_BUILD_MACOS` | `CIBW_BEFORE_BUILD_WINDOWS` | `CIBW_BEFORE_BUILD_LINUX` | `CIBW_BEFORE_BUILD_PYODIDE`
#### Examples
@@ -993,6 +1051,7 @@ Default:
- on Linux: `'auditwheel repair -w {dest_dir} {wheel}'`
- on macOS: `'delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}'`
- on Windows: `''`
- on Pyodide: `''`
A shell command to repair a built wheel by copying external library dependencies into the wheel tree and relinking them.
The command is run on each built wheel (except for pure Python ones) before testing it.
@@ -1006,7 +1065,7 @@ The following placeholders must be used inside the command and will be replaced
The command is run in a shell, so you can run multiple commands like `cmd1 && cmd2`.
Platform-specific environment variables are also available:<br/>
`CIBW_REPAIR_WHEEL_COMMAND_MACOS` | `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS` | `CIBW_REPAIR_WHEEL_COMMAND_LINUX`
`CIBW_REPAIR_WHEEL_COMMAND_MACOS` | `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS` | `CIBW_REPAIR_WHEEL_COMMAND_LINUX` | `CIBW_REPAIR_WHEEL_COMMAND_PYODIDE`
!!! tip
cibuildwheel doesn't yet ship a default repair command for Windows.
@@ -1017,6 +1076,12 @@ Platform-specific environment variables are also available:<br/>
[Delvewheel]: https://github.com/adang1345/delvewheel
!!! tip
When using `--platform pyodide`, `pyodide build` is used to do the build,
which already uses `auditwheel-emscripten` to repair the wheel, so the default
repair command is empty. If there is a way to do this in two steps in the future,
this could change.
#### Examples
!!! tab examples "Environment variables"
@@ -1281,7 +1346,7 @@ here and it will be used instead.
`./constraints.txt` if that's not found.
Platform-specific environment variables are also available:<br/>
`CIBW_DEPENDENCY_VERSIONS_MACOS` | `CIBW_DEPENDENCY_VERSIONS_WINDOWS`
`CIBW_DEPENDENCY_VERSIONS_MACOS` | `CIBW_DEPENDENCY_VERSIONS_WINDOWS` | `CIBW_DEPENDENCY_VERSIONS_PYODIDE`
!!! note
This option does not affect the tools used on the Linux build - those versions
@@ -1339,16 +1404,13 @@ not be installed after building.
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
Platform-specific environment variables are also available:<br/>
`CIBW_TEST_COMMAND_MACOS` | `CIBW_TEST_COMMAND_WINDOWS` | `CIBW_TEST_COMMAND_LINUX`
`CIBW_TEST_COMMAND_MACOS` | `CIBW_TEST_COMMAND_WINDOWS` | `CIBW_TEST_COMMAND_LINUX` | `CIBW_TEST_COMMAND_PYODIDE`
#### Examples
!!! tab examples "Environment variables"
```yaml
# Run the project tests against the installed wheel using `nose`
CIBW_TEST_COMMAND: nosetests {project}/tests
# Run the package tests using `pytest`
CIBW_TEST_COMMAND: pytest {package}/tests
@@ -1365,9 +1427,6 @@ Platform-specific environment variables are also available:<br/>
```toml
[tool.cibuildwheel]
# Run the project tests against the installed wheel using `nose`
test-command = "nosetests {project}/tests"
# Run the package tests using `pytest`
test-command = "pytest {package}/tests"
@@ -1402,7 +1461,7 @@ The active Python binary can be accessed using `python`, and pip with `pip`; cib
The command is run in a shell, so you can write things like `cmd1 && cmd2`.
Platform-specific environment variables are also available:<br/>
`CIBW_BEFORE_TEST_MACOS` | `CIBW_BEFORE_TEST_WINDOWS` | `CIBW_BEFORE_TEST_LINUX`
`CIBW_BEFORE_TEST_MACOS` | `CIBW_BEFORE_TEST_WINDOWS` | `CIBW_BEFORE_TEST_LINUX` | `CIBW_BEFORE_TEST_PYODIDE`
#### Examples
@@ -1462,7 +1521,7 @@ Platform-specific environment variables are also available:<br/>
Space-separated list of dependencies required for running the tests.
Platform-specific environment variables are also available:<br/>
`CIBW_TEST_REQUIRES_MACOS` | `CIBW_TEST_REQUIRES_WINDOWS` | `CIBW_TEST_REQUIRES_LINUX`
`CIBW_TEST_REQUIRES_MACOS` | `CIBW_TEST_REQUIRES_WINDOWS` | `CIBW_TEST_REQUIRES_LINUX` | `CIBW_TEST_REQUIRES_PYODIDE`
#### Examples
@@ -1473,7 +1532,7 @@ Platform-specific environment variables are also available:<br/>
CIBW_TEST_REQUIRES: pytest
# Install specific versions of test dependencies
CIBW_TEST_REQUIRES: nose==1.3.7 moto==0.4.31
CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1
```
!!! tab examples "pyproject.toml"
@@ -1485,7 +1544,7 @@ Platform-specific environment variables are also available:<br/>
# Install specific versions of test dependencies
[tool.cibuildwheel]
test-requires = ["nose==1.3.7", "moto==0.4.31"]
test-requires = ["pytest==8.2.2", "packaging==24.1"]
```
In configuration files, you can use an array, and the items will be joined with a space.
@@ -1502,7 +1561,7 @@ tests. This can be used to avoid having to redefine test dependencies in
`setup.cfg` or `setup.py`.
Platform-specific environment variables are also available:<br/>
`CIBW_TEST_EXTRAS_MACOS` | `CIBW_TEST_EXTRAS_WINDOWS` | `CIBW_TEST_EXTRAS_LINUX`
`CIBW_TEST_EXTRAS_MACOS` | `CIBW_TEST_EXTRAS_WINDOWS` | `CIBW_TEST_EXTRAS_LINUX` | `CIBW_TEST_EXTRAS_PYODIDE`
#### Examples
@@ -1575,13 +1634,27 @@ Default: Off (0).
export CIBW_DEBUG_KEEP_CONTAINER=TRUE
```
### `CIBW_DEBUG_TRACEBACK`
> Print full traceback when errors occur.
Print a full traceback for the cibuildwheel process when errors occur. This
option is provided for debugging cibuildwheel.
This option can also be set using the [command-line option](#command-line) `--debug-traceback`.
#### Examples
```shell
export CIBW_DEBUG_TRACEBACK=TRUE
```
### `CIBW_BUILD_VERBOSITY` {: #build-verbosity}
> Increase/decrease the output of pip wheel
A number from 1 to 3 to increase the level of verbosity (corresponding to invoking pip with `-v`, `-vv`, and `-vvv`), between -1 and -3 (`-q`, `-qq`, and `-qqq`), or just 0 (default verbosity). These flags are useful while debugging a build when the output of the actual build invoked by `pip wheel` is required. Has no effect on the `build` backend, which produces verbose output by default.
Platform-specific environment variables are also available:<br/>
`CIBW_BUILD_VERBOSITY_MACOS` | `CIBW_BUILD_VERBOSITY_WINDOWS` | `CIBW_BUILD_VERBOSITY_LINUX`
`CIBW_BUILD_VERBOSITY_MACOS` | `CIBW_BUILD_VERBOSITY_WINDOWS` | `CIBW_BUILD_VERBOSITY_LINUX` | `CIBW_BUILD_VERBOSITY_PYODIDE`
#### Examples
@@ -1601,12 +1674,25 @@ Platform-specific environment variables are also available:<br/>
```
## Command line options {: #command-line}
## Command line {: #command-line}
### Options
```text
« subprocess_run("cibuildwheel", "--help") »
```
### Return codes
cibuildwheel exits 0 on success, or >0 if an error occurs.
Specific error codes are defined:
- 2 means a configuration error
- 3 means no builds are selected (and [`--allow-empty`](#allow-empty) wasn't passed)
- 4 means you specified an option that has been deprecated.
## Placeholders
Some options support placeholders, like `{project}`, `{package}` or `{wheel}`, that are substituted by cibuildwheel before they are used. If, for some reason, you need to write the literal name of a placeholder, e.g. literally `{project}` in a command that would ordinarily substitute `{project}`, prefix it with a hash character - `#{project}`. This is only necessary in commands where the specific string between the curly brackets would be substituted - otherwise, strings not modified.
+9 -2
View File
@@ -107,6 +107,13 @@ You can override the cache folder using the ``CIBW_CACHE_PATH`` environment vari
Download link: https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg
```
### Pyodide (WebAssembly) builds (experimental)
Pre-requisite: you need to have a matching host version of Python (unlike all
other cibuildwheel platforms). Linux host highly recommended; macOS hosts may
work (e.g. invoking `pytest` directly in [`CIBW_TEST_COMMAND`](options.md#test-command) is [currently failing](https://github.com/pyodide/pyodide/issues/4802)) and Windows hosts will not work.
You must target pyodide with `--platform pyodide` (or use `--only` on the identifier).
## Configure a CI service
@@ -154,7 +161,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/
- uses: actions/checkout@v4
- name: Build wheels
run: pipx run cibuildwheel==2.18.1
run: pipx run cibuildwheel==2.21.0
- uses: actions/upload-artifact@v4
with:
@@ -191,7 +198,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/
- uses: actions/setup-python@v5
- name: Install cibuildwheel
run: python -m pip install cibuildwheel==2.18.1
run: python -m pip install cibuildwheel==2.21.0
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
+30 -28
View File
@@ -11,12 +11,12 @@ title: Working examples
| [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The fundamental package for scientific computing with Python. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system |
| [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [Prophet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth. |
| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an in-process SQL OLAP Database Management System |
| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS |
| [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python |
| [Triton][] | ![github icon][] | ![linux icon][] | Self hosted runners |
@@ -38,7 +38,6 @@ title: Working examples
| [UltraJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings |
| [OpenSpiel][] | ![github icon][] | ![apple icon][] ![linux icon][] | OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games. |
| [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. |
| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
| [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI |
| [pyzmq][] | ![github icon][] ![circleci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython on CPython and CFFI on PyPy. ARM wheels for linux are built natively on CircleCI. |
| [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels |
@@ -56,29 +55,29 @@ title: Working examples
| [Wrapt][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python module for decorators, wrappers and monkey patching. |
| [envd][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A machine learning development environment build tool |
| [OpenColorIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A color management framework for visual effects and animation. |
| [SimpleJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | simplejson is a simple, fast, extensible JSON encoder/decoder for Python |
| [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python |
| [Psycopg 3][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A modern implementation of a PostgreSQL adapter for Python |
| [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python |
| [SimpleJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | simplejson is a simple, fast, extensible JSON encoder/decoder for Python |
| [ruptures][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. |
| [OpenTimelineIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Open Source API and interchange format for editorial timeline information. |
| [PyTables][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data |
| [Parselmouth][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. |
| [google neuroglancer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data |
| [Parselmouth][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. |
| [DeepForest][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | An Efficient, Scalable and Optimized Python Framework for Deep Forest (2021.2.1) |
| [AutoPy][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
| [H3-py][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for H3, a hierarchical hexagonal geospatial indexing system |
| [mosec][] | ![github icon][] | ![linux icon][] ![apple icon][] | A machine learning model serving framework powered by Rust |
| [Picologging][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A high-performance logging library for Python. |
| [time-machine][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Time mocking library using only the CPython C API. |
| [Picologging][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A high-performance logging library for Python. |
| [markupsafe][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Safely add untrusted strings to HTML/XML markup. |
| [Rtree][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Rtree: spatial index for Python GIS |
| [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a CMake-based build system |
| [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python |
| [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub |
| [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] ![appveyor icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson |
| [tgcalls][] | ![github icon][] | ![apple icon][] ![windows icon][] | Python `pybind11` binding to Telegram's WebRTC library with third party dependencies like `OpenSSL`, `MozJPEG`, `FFmpeg`, etc. |
| [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library |
| [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] ![appveyor icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson |
| [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system |
| [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library |
| [abess][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A fast best-subset selection library. It uses cibuildwheel to build a large project with C++ extensions. |
| [sourmash][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Quickly search, compare, and analyze genomic and metagenomic data sets. |
| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing |
@@ -91,41 +90,43 @@ title: Working examples
| [pillow-heif][] | ![github icon][] ![cirrusci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Bindings to libheif library with third party dependencies. Fully automated CI for tests and publishing including Apple Silicon builds. |
| [TgCrypto][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
| [iDynTree][] | ![github icon][] | ![linux icon][] | Uses manylinux_2_24 |
| [streaming-form-data][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Streaming parser for multipart/form-data written in Cython |
| [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. |
| [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. |
| [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 |
| [power-grid-model][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python/C++ library for distribution power system analysis |
| [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 |
| [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast Base64 encoding/decoding in Python |
| [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms |
| [Imagecodecs (fork)][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. |
| [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms |
| [Arbor][] | ![github icon][] | ![apple icon][] ![linux icon][] | Arbor is a multi-compartment neuron simulation library; compatible with next-generation accelerators; best-practices applied to research software; focused on community-driven development. Includes a [small script](https://github.com/arbor-sim/arbor/blob/master/scripts/patchwheel.py) patching `rpath` in bundled libraries. |
| [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 |
| [polaroid][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Full range of wheels for setuptools rust, with auto release and PyPI deploy. |
| [clang-format][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Scikit-build wrapper around LLVM's CMake, all platforms, generic wheels. |
| [etebase-py][] | ![travisci icon][] | ![linux icon][] | Python bindings to a Rust library using `setuptools-rust`, and `sccache` for improved speed. |
| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
| [cf-units][] | ![github icon][] | ![apple icon][] ![linux icon][] | Units of measure as required by the Climate and Forecast (CF) Metadata Conventions |
| [ninja][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. |
| [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy |
| [pyjet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The interface between FastJet and NumPy |
| [ril][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A python binding to Rust Imaging library using maturin and Pyo3, utilizes Github Action cache to improve speed. Builds abi3 wheels. |
| [GSD][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Cython and NumPy project with 64-bit wheels. |
| [SiPM][] | ![github icon][] | ![apple icon][] ![linux icon][] | High performance library for SiPM detectors simulation using C++17, OpenMP and AVX2 intrinsics. |
| [CorrectionLib][] | ![github icon][] | ![apple icon][] ![linux icon][] | Structured JSON powered correction library for HEP, designed for the CMS experiment at CERN. |
| [GSD][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Cython and NumPy project with 64-bit wheels. |
| [aalink][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Async Python interface for Ableton Link. |
| [CorrectionLib][] | ![github icon][] | ![apple icon][] ![linux icon][] | Structured JSON powered correction library for HEP, designed for the CMS experiment at CERN. |
| [xmlstarlet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. |
| [pyinstrument_cext][] | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies |
| [werpy][] | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | An ultra-fast python package using optimized dynamic programming to compute the Word Error Rate (WER). |
| [pyinstrument_cext][] | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies |
| [pybind11 cross build example][] | ![github icon][] ![gitlab icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Same as pybind11 cmake_example but used to demo Linux ARM + Windows + macOS builds on GitLab |
[scikit-learn]: https://github.com/scikit-learn/scikit-learn
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NumPy]: https://github.com/numpy/numpy
[duckdb]: https://github.com/duckdb/duckdb
[Tornado]: https://github.com/tornadoweb/tornado
[Matplotlib]: https://github.com/matplotlib/matplotlib
[NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib
[Prophet]: https://github.com/facebook/prophet
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[duckdb]: https://github.com/duckdb/duckdb
[Kivy]: https://github.com/kivy/kivy
[MemRay]: https://github.com/bloomberg/memray
[Triton]: https://github.com/openai/triton
@@ -147,7 +148,6 @@ title: Working examples
[UltraJSON]: https://github.com/ultrajson/ultrajson
[OpenSpiel]: https://github.com/google-deepmind/open_spiel
[aiortc]: https://github.com/aiortc/aiortc
[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
[Dependency Injector]: https://github.com/ets-labs/python-dependency-injector
[pyzmq]: https://github.com/zeromq/pyzmq
[Implicit]: https://github.com/benfred/implicit
@@ -165,29 +165,29 @@ title: Working examples
[Wrapt]: https://github.com/GrahamDumpleton/wrapt
[envd]: https://github.com/tensorchord/envd
[OpenColorIO]: https://github.com/AcademySoftwareFoundation/OpenColorIO
[SimpleJSON]: https://github.com/simplejson/simplejson
[aioquic]: https://github.com/aiortc/aioquic
[Psycopg 3]: https://github.com/psycopg/psycopg
[aioquic]: https://github.com/aiortc/aioquic
[SimpleJSON]: https://github.com/simplejson/simplejson
[ruptures]: https://github.com/deepcharles/ruptures
[OpenTimelineIO]: https://github.com/AcademySoftwareFoundation/OpenTimelineIO
[PyTables]: https://github.com/PyTables/PyTables
[Parselmouth]: https://github.com/YannickJadoul/Parselmouth
[google neuroglancer]: https://github.com/google/neuroglancer
[Parselmouth]: https://github.com/YannickJadoul/Parselmouth
[DeepForest]: https://github.com/LAMDA-NJU/Deep-Forest
[AutoPy]: https://github.com/autopilot-rs/autopy
[H3-py]: https://github.com/uber/h3-py
[mosec]: https://github.com/mosecorg/mosec
[Picologging]: https://github.com/microsoft/picologging
[time-machine]: https://github.com/adamchainz/time-machine
[Picologging]: https://github.com/microsoft/picologging
[markupsafe]: https://github.com/pallets/markupsafe
[Rtree]: https://github.com/Toblerity/rtree
[pybind11 cmake_example]: https://github.com/pybind/cmake_example
[KDEpy]: https://github.com/tommyod/KDEpy
[dd-trace-py]: https://github.com/DataDog/dd-trace-py
[python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson
[tgcalls]: https://github.com/MarshalX/tgcalls
[python-snappy]: https://github.com/intake/python-snappy
[python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson
[pybind11 python_example]: https://github.com/pybind/python_example
[python-snappy]: https://github.com/intake/python-snappy
[abess]: https://github.com/abess-team/abess
[sourmash]: https://github.com/sourmash-bio/sourmash
[cyvcf2]: https://github.com/brentp/cyvcf2
@@ -200,30 +200,32 @@ title: Working examples
[pillow-heif]: https://github.com/bigcat88/pillow_heif
[TgCrypto]: https://github.com/pyrogram/tgcrypto
[iDynTree]: https://github.com/robotology/idyntree
[streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data
[bx-python]: https://github.com/bxlab/bx-python
[boost-histogram]: https://github.com/scikit-hep/boost-histogram
[Python-WebRTC]: https://github.com/MarshalX/python-webrtc
[power-grid-model]: https://github.com/PowerGridModel/power-grid-model
[Python-WebRTC]: https://github.com/MarshalX/python-webrtc
[pybase64]: https://github.com/mayeut/pybase64
[fathon]: https://github.com/stfbnc/fathon
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build
[fathon]: https://github.com/stfbnc/fathon
[Arbor]: https://github.com/arbor-sim/arbor
[pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example
[polaroid]: https://github.com/daggy1234/polaroid
[clang-format]: https://github.com/ssciwr/clang-format-wheel
[etebase-py]: https://github.com/etesync/etebase-py
[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
[cf-units]: https://github.com/SciTools/cf-units
[ninja]: https://github.com/scikit-build/ninja-python-distributions
[numpythia]: https://github.com/scikit-hep/numpythia
[pyjet]: https://github.com/scikit-hep/pyjet
[ril]: https://github.com/Cryptex-github/ril-py
[GSD]: https://github.com/glotzerlab/gsd
[SiPM]: https://github.com/EdoPro98/SimSiPM
[CorrectionLib]: https://github.com/cms-nanoAOD/correctionlib
[GSD]: https://github.com/glotzerlab/gsd
[aalink]: https://github.com/artfwo/aalink
[CorrectionLib]: https://github.com/cms-nanoAOD/correctionlib
[xmlstarlet]: https://github.com/dimitern/xmlstarlet
[pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext
[werpy]: https://github.com/analyticsinmotion/werpy
[pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext
[pybind11 cross build example]: https://github.com/wbarnha/pybind_cmake_example_crossbuild
[appveyor icon]: data/readme_icons/appveyor.svg
+8 -11
View File
@@ -1,18 +1,15 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
APPVEYOR_JOB_NAME: "python37-x64-ubuntu"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
APPVEYOR_JOB_NAME: "python37-x64-vs2015"
- APPVEYOR_BUILD_WORKER_IMAGE: macos
APPVEYOR_JOB_NAME: "python37-x64-macos"
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204
APPVEYOR_JOB_NAME: "linux-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
APPVEYOR_JOB_NAME: "windows-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma
APPVEYOR_JOB_NAME: "macos-x64"
stack: python 3.7
stack: python 3.12
init:
- cmd: set PATH=C:\Python37;C:\Python37\Scripts;%PATH%
install: python -m pip install cibuildwheel==2.18.1
install: python -m pip install cibuildwheel==2.21.0
build_script: python -m cibuildwheel --output-dir wheelhouse
+4 -4
View File
@@ -6,7 +6,7 @@ jobs:
- bash: |
set -o errexit
python3 -m pip install --upgrade pip
pip3 install cibuildwheel==2.18.1
pip3 install cibuildwheel==2.21.0
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
@@ -14,13 +14,13 @@ jobs:
inputs: {pathtoPublish: 'wheelhouse'}
- job: macos
pool: {vmImage: 'macOS-11'}
pool: {vmImage: 'macOS-12'}
steps:
- task: UsePythonVersion@0
- bash: |
set -o errexit
python3 -m pip install --upgrade pip
python3 -m pip install cibuildwheel==2.18.1
python3 -m pip install cibuildwheel==2.21.0
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
@@ -34,7 +34,7 @@ jobs:
- bash: |
set -o errexit
python -m pip install --upgrade pip
pip install cibuildwheel==2.18.1
pip install cibuildwheel==2.21.0
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
+7 -5
View File
@@ -4,14 +4,14 @@ jobs:
linux-wheels:
working_directory: ~/linux-wheels
docker:
- image: circleci/python:3.9
- image: cimg/python:3.12
steps:
- checkout
- setup_remote_docker
- run:
name: Build the Linux wheels.
command: |
pip3 install --user cibuildwheel==2.18.1
python3 -m pip install --user cibuildwheel==2.21.0
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
@@ -28,7 +28,7 @@ jobs:
- run:
name: Build the Linux aarch64 wheels.
command: |
python3 -m pip install --user cibuildwheel==2.18.1
python3 -m pip install --user cibuildwheel==2.21.0
python3 -m cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
@@ -36,13 +36,15 @@ jobs:
osx-wheels:
working_directory: ~/osx-wheels
macos:
xcode: 12.5.1
xcode: 15.4.0
resource_class: macos.m1.medium.gen1
steps:
- checkout
- run:
name: Build the OS X wheels.
command: |
pip3 install cibuildwheel==2.18.1
sudo softwareupdate --install-rosetta --agree-to-license # for python<=3.8 or x86_64/universal2 tests
pip3 install cibuildwheel==2.21.0
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
+5 -4
View File
@@ -1,6 +1,6 @@
build_and_store_wheels: &BUILD_AND_STORE_WHEELS
install_cibuildwheel_script:
- python -m pip install cibuildwheel==2.18.1
- python -m pip install cibuildwheel==2.21.0
run_cibuildwheel_script:
- cibuildwheel
wheels_artifacts:
@@ -11,10 +11,11 @@ macos_task:
name: Build macOS x86_64 and arm64 wheels.
macos_instance:
image: ghcr.io/cirruslabs/macos-sonoma-xcode
env:
PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
CIBW_ARCHS_MACOS: x86_64 arm64
install_pre_requirements_script:
- brew install python@3.10
- brew install python@3.12
- python3.12 -m venv ${VENV_ROOT}
<<: *BUILD_AND_STORE_WHEELS
+20 -9
View File
@@ -1,6 +1,6 @@
build_and_store_wheels: &BUILD_AND_STORE_WHEELS
install_cibuildwheel_script:
- python -m pip install cibuildwheel==2.18.1
- python -m pip install cibuildwheel==2.21.0
run_cibuildwheel_script:
- cibuildwheel
wheels_artifacts:
@@ -15,9 +15,14 @@ linux_x86_task:
platform: linux
cpu: 4
memory: 4G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- apt install -y python3-venv python-is-python3
- add-apt-repository -y ppa:deadsnakes/ppa
- apt-get update
- apt-get install -y python3.12-venv
- python3.12 -m venv ${VENV_ROOT}
<<: *BUILD_AND_STORE_WHEELS
linux_aarch64_task:
@@ -29,9 +34,14 @@ linux_aarch64_task:
platform: linux
cpu: 4
memory: 4G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- apt install -y python3-venv python-is-python3
- add-apt-repository -y ppa:deadsnakes/ppa
- apt-get update
- apt-get install -y python3.12-venv
- python3.12 -m venv ${VENV_ROOT}
<<: *BUILD_AND_STORE_WHEELS
windows_x86_task:
@@ -42,7 +52,7 @@ windows_x86_task:
memory: 4G
install_pre_requirements_script:
- choco install -y --no-progress python3 --version 3.10.6
- choco install -y --no-progress python3 --version 3.12.4
- refreshenv
- echo PATH=%PATH% >> "%CIRRUS_ENV%"
<<: *BUILD_AND_STORE_WHEELS
@@ -51,9 +61,10 @@ macos_arm64_task:
name: Build macOS arm64 wheels.
macos_instance:
image: ghcr.io/cirruslabs/macos-sonoma-xcode
env:
PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- brew install python@3.10
- brew install python@3.12
- python3.12 -m venv ${VENV_ROOT}
<<: *BUILD_AND_STORE_WHEELS
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v4
- name: Build wheels
uses: pypa/cibuildwheel@v2.18.1
uses: pypa/cibuildwheel@v2.21.0
- uses: actions/upload-artifact@v4
with:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4
- name: Build wheels
uses: pypa/cibuildwheel@v2.18.1
uses: pypa/cibuildwheel@v2.21.0
# env:
# CIBW_SOME_OPTION: value
# ...
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
platforms: all
- name: Build wheels
uses: pypa/cibuildwheel@v2.18.1
uses: pypa/cibuildwheel@v2.21.0
env:
# configure cibuildwheel to build native archs ('auto'), and some
# emulated ones
+17 -5
View File
@@ -1,5 +1,5 @@
linux:
image: python:3.8
image: python:3.12
# make a docker daemon available for cibuildwheel to use
services:
- name: docker:dind
@@ -12,7 +12,7 @@ linux:
DOCKER_TLS_CERTDIR: ""
script:
- curl -sSL https://get.docker.com/ | sh
- python -m pip install cibuildwheel==2.18.1
- python -m pip install cibuildwheel==2.21.0
- cibuildwheel --output-dir wheelhouse
artifacts:
paths:
@@ -21,13 +21,25 @@ linux:
windows:
image: mcr.microsoft.com/windows/servercore:1809
before_script:
- choco install python -y --version 3.8.6
- choco install python -y --version 3.12.4
- choco install git.install -y
- py -m pip install cibuildwheel==2.18.1
- py -m pip install cibuildwheel==2.21.0
script:
- py -m cibuildwheel --output-dir wheelhouse --platform windows
artifacts:
paths:
- wheelhouse/
tags:
- windows
- saas-windows-medium-amd64
macos:
image: macos-14-xcode-15
before_script:
- python3 -m pip install cibuildwheel==2.21.0
script:
- python3 -m cibuildwheel --output-dir wheelhouse
artifacts:
paths:
- wheelhouse/
tags:
- saas-macos-medium-m1
+2 -2
View File
@@ -1,5 +1,5 @@
linux:
image: python:3.8
image: python:3.12
# make a docker daemon available for cibuildwheel to use
services:
- name: docker:dind
@@ -14,7 +14,7 @@ linux:
- curl -sSL https://get.docker.com/ | sh
# Warning: This is extremely slow, be careful with how many wheels you build
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- python -m pip install cibuildwheel==2.18.1
- python -m pip install cibuildwheel==2.21.0
# Assuming your CI runner's default architecture is x86_64...
- cibuildwheel --output-dir wheelhouse --platform linux --archs aarch64
artifacts:
+5 -5
View File
@@ -4,7 +4,7 @@
os: linux
dist: focal
language: python
python: "3.9"
python: "3.12"
jobs:
include:
@@ -14,13 +14,13 @@ jobs:
- os: windows
language: shell
before_install:
- choco upgrade python -y --version 3.8.6
- export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
- choco upgrade python -y --version 3.12.4
- export PATH="/c/Python312:/c/Python312/Scripts:$PATH"
# make sure it's on PATH as 'python3'
- ln -s /c/Python38/python.exe /c/Python38/python3.exe
- ln -s /c/Python312/python.exe /c/Python312/python3.exe
install:
- python3 -m pip install cibuildwheel==2.18.1
- python3 -m pip install cibuildwheel==2.21.0
script:
# build the wheels, put them into './dist'
+5 -5
View File
@@ -1,7 +1,7 @@
os: linux
dist: focal
language: python
python: "3.9"
python: "3.12"
jobs:
include:
@@ -20,13 +20,13 @@ jobs:
- os: windows
language: shell
before_install:
- choco upgrade python -y --version 3.8.6
- export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
- choco upgrade python -y --version 3.12.4
- export PATH="/c/Python312:/c/Python312/Scripts:$PATH"
# make sure it's on PATH as 'python3'
- ln -s /c/Python38/python.exe /c/Python38/python3.exe
- ln -s /c/Python312/python.exe /c/Python312/python3.exe
install:
- python3 -m pip install cibuildwheel==2.18.1
- python3 -m pip install cibuildwheel==2.21.0
script:
# build the wheels, put them into './wheelhouse'
+6 -8
View File
@@ -8,17 +8,15 @@
os: linux
dist: focal
language: python
python:
- 3.7
- 3.8
python: "3.12"
before_install:
- |
if [[ "$TRAVIS_OS_NAME" = windows ]]; then
choco upgrade python -y --version 3.8.6
export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
choco upgrade python -y --version 3.12.4
export PATH="/c/Python312:/c/Python312/Scripts:$PATH"
# make sure it's on PATH as 'python3'
ln -s /c/Python38/python.exe /c/Python38/python3.exe
ln -s /c/Python312/python.exe /c/Python312/python3.exe
fi
install:
@@ -54,7 +52,7 @@ jobs:
- stage: deploy
name: Build and deploy Linux wheels
services: docker
install: python3 -m pip install cibuildwheel==2.18.1 twine
install: python3 -m pip install cibuildwheel==2.21.0 twine
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl
# Deploy on windows
@@ -62,7 +60,7 @@ jobs:
name: Build and deploy Windows wheels
os: windows
language: shell
install: python3 -m pip install cibuildwheel==2.18.1 twine
install: python3 -m pip install cibuildwheel==2.21.0 twine
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl
+28 -5
View File
@@ -70,6 +70,8 @@ def update_constraints(session: nox.Session) -> None:
Update the dependencies inplace.
"""
resources = Path("cibuildwheel/resources")
if session.venv_backend != "uv":
session.install("uv>=0.1.23")
@@ -79,22 +81,42 @@ def update_constraints(session: nox.Session) -> None:
# CUSTOM_COMPILE_COMMAND is a pip-compile option that tells users how to
# regenerate the constraints files
env["UV_CUSTOM_COMPILE_COMMAND"] = f"nox -s {session.name}"
output_file = resources / f"constraints-python{python_version.replace('.', '')}.txt"
session.run(
"uv",
"pip",
"compile",
f"--python-version={python_version}",
"--upgrade",
"cibuildwheel/resources/constraints.in",
f"--output-file=cibuildwheel/resources/constraints-python{python_version.replace('.', '')}.txt",
resources / "constraints.in",
f"--output-file={output_file}",
env=env,
)
RESOURCES = DIR / "cibuildwheel" / "resources"
shutil.copyfile(
RESOURCES / "constraints-python312.txt",
RESOURCES / "constraints.txt",
resources / "constraints-python312.txt",
resources / "constraints.txt",
)
build_platforms = nox.project.load_toml(resources / "build-platforms.toml")
pyodides = build_platforms["pyodide"]["python_configurations"]
for pyodide in pyodides:
python_version = ".".join(pyodide["version"].split(".")[:2])
pyodide_version = pyodide["pyodide_version"]
output_file = resources / f"constraints-pyodide{python_version.replace('.', '')}.txt"
tmp_file = Path(session.create_tmp()) / "constraints-pyodide.in"
tmp_file.write_text(f"pip\nbuild[virtualenv]\npyodide-build=={pyodide_version}")
session.run(
"uv",
"pip",
"compile",
f"--python-version={python_version}",
"--upgrade",
tmp_file,
f"--output-file={output_file}",
env=env,
)
@nox.session
def update_pins(session: nox.Session) -> None:
@@ -105,6 +127,7 @@ def update_pins(session: nox.Session) -> None:
session.run("python", "bin/update_pythons.py", "--force")
session.run("python", "bin/update_docker.py")
session.run("python", "bin/update_virtualenv.py", "--force")
session.run("python", "bin/update_nodejs.py", "--force")
@nox.session(reuse_venv=True)
+6 -3
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "cibuildwheel"
version = "2.18.1"
version = "2.21.0"
description = "Build Python wheels on CI with minimal configuration."
readme = "README.md"
license = "BSD-2-Clause"
@@ -75,9 +75,11 @@ test = [
"pytest-timeout",
"pytest-xdist",
"pytest>=6",
"setuptools",
"tomli_w",
"validate-pyproject",
]
uv = ["uv"]
[project.scripts]
cibuildwheel = "cibuildwheel.__main__:main"
@@ -134,6 +136,8 @@ disallow_untyped_decorators = true
[[tool.mypy.overrides]]
module = [
"setuptools",
"setuptools._distutils", # needed even if only directly import setuptools._distutils.util
"setuptools._distutils.util",
"pytest", # ignored in pre-commit to speed up check
"bashlex",
"bashlex.*",
@@ -176,13 +180,12 @@ messages_control.disable = [
"wrong-import-position",
"unused-argument", # Handled by Ruff
"broad-exception-raised", # Could be improved eventually
"consider-using-in", # MyPy can't narrow "in"
]
[tool.ruff]
target-version = "py38"
line-length = 100
[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
+24 -9
View File
@@ -6,14 +6,18 @@ from typing import Generator
import pytest
from cibuildwheel.util import detect_ci_provider
from cibuildwheel.util import detect_ci_provider, find_uv
from .utils import platform
from .utils import EMULATED_ARCHS, platform
def pytest_addoption(parser) -> None:
parser.addoption(
"--run-emulation", action="store_true", default=False, help="run emulation tests"
"--run-emulation",
action="store",
default=None,
help="run emulation tests",
choices=("all", *EMULATED_ARCHS),
)
parser.addoption("--run-podman", action="store_true", default=False, help="run podman tests")
parser.addoption(
@@ -24,14 +28,25 @@ def pytest_addoption(parser) -> None:
)
@pytest.fixture(
params=[{"CIBW_BUILD_FRONTEND": "pip"}, {"CIBW_BUILD_FRONTEND": "build"}], ids=["pip", "build"]
)
def build_frontend_env(request) -> dict[str, str]:
return request.param # type: ignore[no-any-return]
@pytest.fixture(params=["pip", "build"])
def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]:
frontend = request.param
if platform == "pyodide" and frontend == "pip":
pytest.skip("Can't use pip as build frontend for pyodide platform")
return {"CIBW_BUILD_FRONTEND": frontend}
@pytest.fixture()
@pytest.fixture
def build_frontend_env(build_frontend_env_nouv: dict[str, str]) -> dict[str, str]:
frontend = build_frontend_env_nouv["CIBW_BUILD_FRONTEND"]
if frontend != "build" or platform == "pyodide" or find_uv() is None:
return build_frontend_env_nouv
return {"CIBW_BUILD_FRONTEND": "build[uv]"}
@pytest.fixture
def docker_cleanup() -> Generator[None, None, None]:
def get_images() -> set[str]:
if detect_ci_provider() is None or platform != "linux":
+11 -3
View File
@@ -69,7 +69,15 @@ def test_build_identifiers(tmp_path):
), f"{expected_wheels} vs {build_identifiers}"
def test_allow_empty(tmp_path):
@pytest.mark.parametrize(
("add_args", "env_allow_empty"),
[
(["--allow-empty"], {}),
(["--allow-empty"], {"CIBW_ALLOW_EMPTY": "0"}),
(None, {"CIBW_ALLOW_EMPTY": "1"}),
],
)
def test_allow_empty(tmp_path, add_args, env_allow_empty):
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
@@ -77,8 +85,8 @@ def test_allow_empty(tmp_path):
# without error
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL"},
add_args=["--allow-empty"],
add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL", **env_allow_empty},
add_args=add_args,
)
# check that nothing was built
+26 -13
View File
@@ -20,7 +20,7 @@ limited_api_project = test_projects.new_c_project(
CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED
cmdclass = {}
extension_kwargs = {}
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 8):
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10):
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
class bdist_wheel_abi3(_bdist_wheel):
@@ -33,7 +33,7 @@ limited_api_project = test_projects.new_c_project(
return python, "abi3", plat
cmdclass["bdist_wheel"] = bdist_wheel_abi3
extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x03080000")]
extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x030A0000")]
extension_kwargs["py_limited_api"] = True
"""
),
@@ -48,23 +48,32 @@ def test_abi3(tmp_path):
project_dir = tmp_path / "project"
limited_api_project.generate(project_dir)
single_python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
# free_threaded and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
"CIBW_BUILD": "cp3?-* cp31?-* cp313t-* pp310-*"
# also limit the number of builds for test performance reasons
"CIBW_BUILD": f"cp39-* cp310-* pp310-* {single_python_tag}-* cp313t-*"
},
)
# check that the expected wheels are produced
expected_wheels = [
w.replace("cp38-cp38", "cp38-abi3")
for w in utils.expected_wheels("spam", "0.1.0")
if ("-pp310" in w or "-pp" not in w)
and "-cp39" not in w
and ("-cp313t" in w or "-cp31" not in w)
]
expected_wheels = utils.expected_wheels("spam", "0.1.0")
if utils.platform == "pyodide":
# there's only 1 possible configuration for pyodide, the single_python_tag one
expected_wheels = [
w.replace(f"{single_python_tag}-{single_python_tag}", f"{single_python_tag}-abi3")
for w in expected_wheels
]
else:
expected_wheels = [
w.replace("cp310-cp310", "cp310-abi3")
for w in expected_wheels
if "-cp39" in w or "-cp310" in w or "-pp310" in w or "-cp313t" in w
]
assert set(actual_wheels) == set(expected_wheels)
@@ -183,9 +192,9 @@ def test_abi_none(tmp_path, capfd):
project_dir,
add_env={
"CIBW_TEST_REQUIRES": "pytest",
"CIBW_TEST_COMMAND": "pytest {project}/test",
"CIBW_TEST_COMMAND": f"{utils.invoke_pytest()} {{project}}/test",
# limit the number of builds for test performance reasons
"CIBW_BUILD": "cp38-* cp310-* cp313t-* pp310-*",
"CIBW_BUILD": "cp38-* cp{}{}-* cp313t-* pp310-*".format(*utils.SINGLE_PYTHON_VERSION),
},
)
@@ -196,4 +205,8 @@ def test_abi_none(tmp_path, capfd):
# check that each wheel was built once, and reused
captured = capfd.readouterr()
assert "Building wheel..." in captured.out
assert "Found previously built wheel" in captured.out
if utils.platform == "pyodide":
# there's only 1 possible configuration for pyodide, we won't see the message expected on following builds
assert "Found previously built wheel" not in captured.out
else:
assert "Found previously built wheel" in captured.out
+21 -8
View File
@@ -10,13 +10,22 @@ from . import test_projects, utils
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
# assert that the Python version as written to text_info.txt in the CIBW_BEFORE_ALL step
# is the same one as is currently running.
import os
with open("text_info.txt") as f:
stored_text = f.read()
print("## stored text: " + stored_text)
assert stored_text == "sample text 123"
# assert that the Python version as written to python_prefix.txt in the CIBW_BEFORE_ALL step
# is not the same one as is currently running.
with open('python_prefix.txt') as f:
stored_prefix = f.read()
print('stored_prefix', stored_prefix)
print('sys.prefix', sys.prefix)
# Works around path-comparison bugs caused by short-paths on Windows e.g.
# vssadm~1 instead of vssadministrator
assert not os.path.samefile(stored_prefix, sys.prefix)
"""
)
)
@@ -30,21 +39,24 @@ def test(tmp_path):
print("dummy text", file=ff)
# build the wheels
before_all_command = '''python -c "import os;open('{project}/text_info.txt', 'w').write('sample text '+os.environ.get('TEST_VAL', ''))"'''
before_all_command = (
"""python -c "import os, sys;open('{project}/text_info.txt', 'w').write('sample text '+os.environ.get('TEST_VAL', ''))" && """
'''python -c "import sys; open('{project}/python_prefix.txt', 'w').write(sys.prefix)"'''
)
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
# write python version information to a temporary file, this is
# checked in setup.py
"CIBW_BEFORE_ALL": before_all_command,
"CIBW_BEFORE_ALL_LINUX": f'{before_all_command} && python -c "import sys; assert sys.version_info >= (3, 6)"',
"CIBW_BEFORE_ALL_LINUX": f'{before_all_command} && python -c "import sys; assert sys.version_info >= (3, 8)"',
"CIBW_ENVIRONMENT": "TEST_VAL='123'",
},
single_python=True,
)
# also check that we got the right wheels
(project_dir / "text_info.txt").unlink()
expected_wheels = utils.expected_wheels("spam", "0.1.0")
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
@@ -72,7 +84,8 @@ def test_cwd(tmp_path):
"CIBW_BEFORE_ALL": f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
"CIBW_BEFORE_ALL_LINUX": '''python -c "import os; assert os.getcwd() == '/project'"''',
},
single_python=True,
)
expected_wheels = utils.expected_wheels("spam", "0.1.0")
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
+19 -20
View File
@@ -7,33 +7,32 @@ import pytest
from . import test_projects, utils
# pyodide does not support building without isolation, need to check the base_prefix
SYS_PREFIX = f"sys.{'base_' if utils.platform == 'pyodide' else ''}prefix"
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
rf"""
import os
# assert that the Python version as written to pythonversion_bb.txt in the CIBW_BEFORE_BUILD step
# is the same one as is currently running.
version_file = 'c:\\pythonversion_bb.txt' if sys.platform == 'win32' else '/tmp/pythonversion_bb.txt'
with open(version_file) as f:
with open('pythonversion_bb.txt') as f:
stored_version = f.read()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
# check that the executable also was written
executable_file = 'c:\\pythonexecutable_bb.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable_bb.txt'
with open(executable_file) as f:
stored_executable = f.read()
print('stored_executable', stored_executable)
print('sys.executable', sys.executable)
# check that the prefix also was written
with open('pythonprefix_bb.txt') as f:
stored_prefix = f.read()
print('stored_prefix', stored_prefix)
print('{SYS_PREFIX}', {SYS_PREFIX})
# Works around path-comparison bugs caused by short-paths on Windows e.g.
# vssadm~1 instead of vssadministrator
# windows/mac are case insensitive
stored_path = os.path.realpath(stored_executable).lower()
current_path = os.path.realpath(sys.executable).lower()
# TODO: This is not valid in an virtual environment
assert stored_path == current_path, '{0} != {1}'.format(stored_path, current_path)
assert os.path.samefile(stored_prefix, {SYS_PREFIX})
"""
)
)
@@ -44,8 +43,8 @@ def test(tmp_path):
project_with_before_build_asserts.generate(project_dir)
before_build = (
"""python -c "import sys; open('{output_dir}pythonversion_bb.txt', 'w').write(sys.version)" && """
'''python -c "import sys; open('{output_dir}pythonexecutable_bb.txt', 'w').write(sys.executable)"'''
"""python -c "import sys; open('{project}/pythonversion_bb.txt', 'w').write(sys.version)" && """
f'''python -c "import sys; open('{{project}}/pythonprefix_bb.txt', 'w').write({SYS_PREFIX})"'''
)
# build the wheels
@@ -54,8 +53,7 @@ def test(tmp_path):
add_env={
# write python version information to a temporary file, this is
# checked in setup.py
"CIBW_BEFORE_BUILD": before_build.format(output_dir="/tmp/"),
"CIBW_BEFORE_BUILD_WINDOWS": before_build.format(output_dir=r"c:\\"),
"CIBW_BEFORE_BUILD": before_build,
},
)
@@ -88,7 +86,8 @@ def test_cwd(tmp_path):
"CIBW_BEFORE_BUILD": f'''python -c "import os; assert os.getcwd() == {str(project_dir)!r}"''',
"CIBW_BEFORE_BUILD_LINUX": '''python -c "import os; assert os.getcwd() == '/project'"''',
},
single_python=True,
)
expected_wheels = utils.expected_wheels("spam", "0.1.0")
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
+24 -11
View File
@@ -6,54 +6,67 @@ before_test_project = test_projects.new_c_project()
before_test_project.files["test/spam_test.py"] = r"""
import sys
import os
from pathlib import Path
from unittest import TestCase
PROJECT_DIR = Path(__file__).joinpath("..", "..").resolve()
class TestBeforeTest(TestCase):
def test_version(self):
# assert that the Python version as written to pythonversion_bt.txt in the CIBW_BEFORE_TEST step
# is the same one as is currently running.
# because of use symlinks in MacOS run this test is also need
version_file = 'c:\\pythonversion_bt.txt' if sys.platform == 'win32' else '/tmp/pythonversion_bt.txt'
with open(version_file) as f:
stored_version = f.read()
stored_version = PROJECT_DIR.joinpath('pythonversion_bt.txt').read_text()
print('stored_version', stored_version)
print('sys.version', sys.version)
assert stored_version == sys.version
def test_prefix(self):
# check that the prefix also was written
prefix_file = 'c:\\pythonprefix_bt.txt' if sys.platform == 'win32' else '/tmp/pythonprefix_bt.txt'
with open(prefix_file) as f:
stored_prefix = f.read()
stored_prefix = PROJECT_DIR.joinpath('pythonprefix_bt.txt').read_text()
print('stored_prefix', stored_prefix)
print('sys.prefix', sys.prefix)
# Works around path-comparison bugs caused by short-paths on Windows e.g.
# vssadm~1 instead of vssadministrator
assert os.stat(stored_prefix) == os.stat(sys.prefix)
assert os.path.samefile(stored_prefix, sys.prefix)
"""
def test(tmp_path):
def test(tmp_path, build_frontend_env):
project_dir = tmp_path / "project"
before_test_project.generate(project_dir)
test_project_dir = project_dir / "dependency"
test_projects.new_c_project().generate(test_project_dir)
before_test_steps = [
'''python -c "import os, sys; open('{project}/pythonversion_bt.txt', 'w').write(sys.version)"''',
'''python -c "import os, sys; open('{project}/pythonprefix_bt.txt', 'w').write(sys.prefix)"''',
]
if utils.platform == "pyodide":
before_test_steps.extend(
["pyodide build {project}/dependency", "pip install --find-links dist/ spam"]
)
elif build_frontend_env["CIBW_BUILD_FRONTEND"] in {"pip", "build"}:
before_test_steps.append("python -m pip install {project}/dependency")
before_test = " && ".join(before_test_steps)
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
# write python version information to a temporary file, this is
# checked in setup.py
"CIBW_BEFORE_TEST": """python -c "import sys; open('/tmp/pythonversion_bt.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonprefix_bt.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency""",
"CIBW_BEFORE_TEST_WINDOWS": """python -c "import sys; open('c:\\pythonversion_bt.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix_bt.txt', 'w').write(sys.prefix)" && python -m pip install {project}/dependency""",
"CIBW_BEFORE_TEST": before_test,
"CIBW_TEST_REQUIRES": "pytest",
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
"CIBW_TEST_COMMAND": "false || pytest {project}/test",
"CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test",
"CIBW_TEST_COMMAND_WINDOWS": "pytest {project}/test",
**build_frontend_env,
},
)
+13 -8
View File
@@ -6,21 +6,24 @@ from . import utils
from .test_projects.c import new_c_project
@pytest.mark.parametrize("frontend_name", ["pip", "build"])
@pytest.mark.parametrize(
"frontend_name",
[
pytest.param("pip", marks=utils.skip_if_pyodide("No pip for pyodide")),
"build",
],
)
def test_build_frontend_args(tmp_path, capfd, frontend_name):
project = new_c_project()
project_dir = tmp_path / "project"
project.generate(project_dir)
# the build will fail because the frontend is called with '-h' - it prints the help message
add_env = {"CIBW_BUILD_FRONTEND": f"{frontend_name}; args: -h"}
if utils.platform == "pyodide":
add_env["TERM"] = "dumb" # disable color / style
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp311-*",
"CIBW_BUILD_FRONTEND": f"{frontend_name}; args: -h",
},
)
utils.cibuildwheel_run(project_dir, add_env=add_env, single_python=True)
captured = capfd.readouterr()
print(captured.out)
@@ -29,6 +32,8 @@ def test_build_frontend_args(tmp_path, capfd, frontend_name):
if frontend_name == "pip":
assert "Usage:" in captured.out
assert "Wheel Options:" in captured.out
elif utils.platform == "pyodide":
assert "Usage: pyodide build" in captured.out
else:
assert "usage:" in captured.out
assert "A simple, correct Python build frontend." in captured.out
+11 -9
View File
@@ -6,12 +6,12 @@ from . import test_projects, utils
project_with_skip_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
# explode if run on PyPyor Python 3.7 (these should be skipped)
rf"""
if sys.implementation.name != "cpython":
raise Exception("Only CPython shall be built")
if sys.version_info[0:2] == (3, 7):
raise Exception("CPython 3.7 should be skipped")
expected_version = {"({}, {})".format(*utils.SINGLE_PYTHON_VERSION)}
if sys.version_info[0:2] != expected_version:
raise Exception("CPython {{}}.{{}} should be skipped".format(*sys.version_info[0:2]))
"""
)
)
@@ -21,17 +21,19 @@ def test(tmp_path):
project_dir = tmp_path / "project"
project_with_skip_asserts.generate(project_dir)
skip = " ".join(
f"cp3{minor}-*" for minor in range(6, 30) if (3, minor) != utils.SINGLE_PYTHON_VERSION
)
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp3*-*",
"CIBW_SKIP": "cp37-*",
"CIBW_SKIP": f"*t-* {skip}",
},
)
# check that we got the right wheels. There should be no PyPy or 3.7.
expected_wheels = [
w for w in utils.expected_wheels("spam", "0.1.0") if ("-cp3" in w) and ("-cp37" not in w)
]
# check that we got the right wheels. There should be a single version of CPython.
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
+8 -10
View File
@@ -21,18 +21,15 @@ def test_podman(tmp_path, capfd, request):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp310-*{manylinux,musllinux}_x86_64",
"CIBW_ARCHS": "native",
"CIBW_BEFORE_ALL": "echo 'test log statement from before-all'",
"CIBW_CONTAINER_ENGINE": "podman",
},
single_python=True,
)
# check that the expected wheels are produced
expected_wheels = [
w
for w in utils.expected_wheels("spam", "0.1.0")
if ("-cp310-" in w) and ("x86_64" in w) and ("manylinux" in w or "musllinux" in w)
]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True, single_arch=True)
assert set(actual_wheels) == set(expected_wheels)
# check that stdout is bring passed-though from container correctly
@@ -51,15 +48,16 @@ def test_create_args(tmp_path, capfd):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp310-manylinux_*",
"CIBW_SKIP": "*-musllinux_*",
"CIBW_BEFORE_ALL": "echo TEST_CREATE_ARGS is set to $TEST_CREATE_ARGS",
"CIBW_CONTAINER_ENGINE": "docker; create_args: --env=TEST_CREATE_ARGS=itworks",
},
single_python=True,
)
expected_wheels = [
w for w in utils.expected_wheels("spam", "0.1.0") if ("cp310-manylinux" in w)
]
expected_wheels = utils.expected_wheels(
"spam", "0.1.0", musllinux_versions=[], single_python=True
)
assert set(actual_wheels) == set(expected_wheels)
captured = capfd.readouterr()
+27 -41
View File
@@ -56,76 +56,62 @@ PyMODINIT_FUNC PyInit_spam(void)
cpp_test_project.files["setup.py"] = jinja2.Template(setup_py_template)
cpp_test_project.files["spam.cpp"] = jinja2.Template(spam_cpp_template)
cpp11_project = cpp_test_project.copy()
cpp11_project.template_context["extra_compile_args"] = (
["/std:c++11"] if utils.platform == "windows" else ["-std=c++11"]
)
cpp11_project.template_context["spam_cpp_top_level_add"] = "#include <array>"
def test_cpp11(tmp_path):
# This test checks that the C++11 standard is supported
project_dir = tmp_path / "project"
cpp11_project = cpp_test_project.copy()
cpp11_project.template_context["extra_compile_args"] = (
["/std:c++11"] if utils.platform == "windows" else ["-std=c++11"]
)
cpp11_project.template_context["spam_cpp_top_level_add"] = "#include <array>"
cpp11_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir)
expected_wheels = list(utils.expected_wheels("spam", "0.1.0"))
actual_wheels = utils.cibuildwheel_run(project_dir, single_python=True)
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
cpp14_project = cpp_test_project.copy()
cpp14_project.template_context["extra_compile_args"] = (
["/std:c++14"] if utils.platform == "windows" else ["-std=c++14"]
)
cpp14_project.template_context["spam_cpp_top_level_add"] = "int a = 100'000;"
def test_cpp14(tmp_path):
# This test checks that the C++14 standard is supported
project_dir = tmp_path / "project"
cpp14_project = cpp_test_project.copy()
cpp14_project.template_context["extra_compile_args"] = (
["/std:c++14"] if utils.platform == "windows" else ["-std=c++14"]
)
cpp14_project.template_context["spam_cpp_top_level_add"] = "int a = 100'000;"
cpp14_project.generate(project_dir)
actual_wheels = utils.cibuildwheel_run(project_dir)
expected_wheels = list(utils.expected_wheels("spam", "0.1.0"))
actual_wheels = utils.cibuildwheel_run(project_dir, single_python=True)
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
cpp17_project = cpp_test_project.copy()
cpp17_project.template_context["extra_compile_args"] = [
"/std:c++17" if utils.platform == "windows" else "-std=c++17"
]
cpp17_project.template_context["spam_cpp_top_level_add"] = r"""
#include <utility>
auto a = std::pair(5.0, false);
"""
def test_cpp17(tmp_path):
# This test checks that the C++17 standard is supported
project_dir = tmp_path / "project"
cpp17_project = cpp_test_project.copy()
cpp17_project.template_context["extra_compile_args"] = [
"/std:c++17" if utils.platform == "windows" else "-std=c++17"
]
cpp17_project.template_context["spam_cpp_top_level_add"] = r"""
#include <utility>
auto a = std::pair(5.0, false);
"""
cpp17_project.generate(project_dir)
if os.environ.get("APPVEYOR_BUILD_WORKER_IMAGE", "") == "Visual Studio 2015":
pytest.skip("Visual Studio 2015 does not support C++17")
# Pypy's distutils sets the default compiler to 'msvc9compiler', which
# is too old to support cpp17.
add_env = {"CIBW_SKIP": "pp*"}
add_env = {}
if utils.platform == "macos":
add_env["MACOSX_DEPLOYMENT_TARGET"] = "10.13"
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
expected_wheels = [
w
for w in utils.expected_wheels("spam", "0.1.0", macosx_deployment_target="10.13")
if "-pp" not in w
]
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env, single_python=True)
expected_wheels = utils.expected_wheels(
"spam", "0.1.0", macosx_deployment_target="10.13", single_python=True
)
assert set(actual_wheels) == set(expected_wheels)
@@ -1,10 +1,12 @@
from __future__ import annotations
import subprocess
from test import test_projects
from contextlib import nullcontext as does_not_raise
import pytest
from test import test_projects
from . import utils
basic_project = test_projects.new_c_project()
@@ -30,8 +32,14 @@ def test(tmp_path, capfd):
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
with pytest.raises(subprocess.CalledProcessError):
utils.cibuildwheel_run(
num_builds = len(utils.cibuildwheel_get_build_identifiers(project_dir))
if num_builds > 1:
expectation = pytest.raises(subprocess.CalledProcessError)
else:
expectation = does_not_raise()
with expectation as exc_info:
result = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_REPAIR_WHEEL_COMMAND": "python repair.py {wheel} {dest_dir}",
@@ -39,4 +47,15 @@ def test(tmp_path, capfd):
)
captured = capfd.readouterr()
assert "Build failed because a wheel named" in captured.err
if num_builds > 1:
assert "Build failed because a wheel named" in captured.err
assert exc_info.value.returncode == 6
else:
# We only produced one wheel (currently Pyodide)
# check that it has the right name
#
# As far as I can tell, this is the only full test coverage for
# CIBW_REPAIR_WHEEL_COMMAND so this is useful even in the case when no
# error is raised
assert "spam-0.1.0-py2-none-pyodide" in captured.out
assert result[0].startswith("spam-0.1.0-py2-none-")
+7 -5
View File
@@ -52,12 +52,14 @@ def get_versions_from_constraint_file(constraint_file):
return dict(re.findall(VERSION_REGEX, constraint_file_text))
@pytest.mark.parametrize("python_version", ["3.6", "3.8", "3.10"])
def test_pinned_versions(tmp_path, python_version, build_frontend_env):
@pytest.mark.parametrize("python_version", ["3.6", "3.8", "3.12"])
def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv):
if utils.platform == "linux":
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
if python_version == "3.6" and utils.platform == "macos" and platform.machine() == "arm64":
pytest.skip("macOS arm64 does not support Python 3.6")
if python_version != "3.12" and utils.platform == "pyodide":
pytest.skip(f"pyodide does not support Python {python_version}")
project_dir = tmp_path / "project"
project_with_expected_version_checks.generate(project_dir)
@@ -79,7 +81,7 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env):
add_env={
"CIBW_BUILD": build_pattern,
"CIBW_ENVIRONMENT": cibw_environment_option,
**build_frontend_env,
**build_frontend_env_nouv,
},
)
@@ -93,7 +95,7 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env):
assert set(actual_wheels) == set(expected_wheels)
def test_dependency_constraints_file(tmp_path, build_frontend_env):
def test_dependency_constraints_file(tmp_path, build_frontend_env_nouv):
if utils.platform == "linux":
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
@@ -130,7 +132,7 @@ def test_dependency_constraints_file(tmp_path, build_frontend_env):
"CIBW_ENVIRONMENT": cibw_environment_option,
"CIBW_DEPENDENCY_VERSIONS": str(constraints_file),
"CIBW_SKIP": "cp36-*",
**build_frontend_env,
**build_frontend_env_nouv,
},
)
+87
View File
@@ -0,0 +1,87 @@
from __future__ import annotations
import shutil
import sys
import textwrap
import pytest
from cibuildwheel.util import CIBW_CACHE_PATH, CIProvider, detect_ci_provider
from . import test_projects, utils
basic_project = test_projects.new_c_project()
basic_project.files["check_node.py"] = r"""
import sys
import shutil
from pathlib import Path
from pyodide.code import run_js
def check_node():
# cibuildwheel adds a pinned node version to the PATH
# check it's in the PATH then, check it's the one that runs pyoodide
cibw_cache_path = Path(sys.argv[1]).resolve(strict=True)
# find the node executable in PATH
node = shutil.which("node")
assert node is not None, "node is None"
node_path = Path(node).resolve(strict=True)
# it shall be in cibuildwheel cache
assert cibw_cache_path in node_path.parents, f"{cibw_cache_path} not a parent of {node_path}"
# find the path to the node executable that runs pyodide
node_js = run_js("globalThis.process.execPath")
assert node_js is not None, "node_js is None"
node_js_path = Path(node_js).resolve(strict=True)
# it shall be the one pinned by cibuildwheel
assert node_js_path == node_path, f"{node_js_path} != {node_path}"
if __name__ == "__main__":
check_node()
"""
@pytest.mark.parametrize("use_pyproject_toml", [True, False])
def test_pyodide_build(tmp_path, use_pyproject_toml):
if sys.platform == "win32":
pytest.skip("emsdk doesn't work correctly on Windows")
if not shutil.which("python3.12"):
pytest.skip("Python 3.12 not installed")
if detect_ci_provider() == CIProvider.travis_ci:
pytest.skip("Python 3.12 is just a non-working pyenv shim")
if use_pyproject_toml:
basic_project.files["pyproject.toml"] = textwrap.dedent(
"""
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
"""
)
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
# check for node in 1 case only to reduce CI load
add_env = {}
if use_pyproject_toml:
add_env["CIBW_TEST_COMMAND"] = f"python {{project}}/check_node.py {CIBW_CACHE_PATH}"
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_args=["--platform", "pyodide"],
add_env=add_env,
)
# check that the expected wheels are produced
expected_wheels = [
"spam-0.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl",
]
print("actual_wheels", actual_wheels)
print("expected_wheels", expected_wheels)
assert set(actual_wheels) == set(expected_wheels)
+10 -6
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import itertools
import subprocess
import pytest
@@ -18,9 +19,13 @@ def test_spam():
def test(tmp_path, request):
if not request.config.getoption("--run-emulation"):
archs = request.config.getoption("--run-emulation")
if archs is None:
pytest.skip("needs --run-emulation option to run")
if archs == "all":
archs = " ".join(utils.EMULATED_ARCHS)
project_dir = tmp_path / "project"
project_with_a_test.generate(project_dir)
@@ -30,15 +35,14 @@ def test(tmp_path, request):
add_env={
"CIBW_TEST_REQUIRES": "pytest",
"CIBW_TEST_COMMAND": "pytest {project}/test",
"CIBW_ARCHS": "aarch64 ppc64le s390x",
"CIBW_ARCHS": archs,
},
)
# also check that we got the right wheels
expected_wheels = (
utils.expected_wheels("spam", "0.1.0", machine_arch="aarch64")
+ utils.expected_wheels("spam", "0.1.0", machine_arch="ppc64le")
+ utils.expected_wheels("spam", "0.1.0", machine_arch="s390x")
expected_wheels = itertools.chain.from_iterable(
utils.expected_wheels("spam", "0.1.0", machine_arch=arch, single_arch=True)
for arch in archs.split(" ")
)
assert set(actual_wheels) == set(expected_wheels)
+15 -6
View File
@@ -49,10 +49,11 @@ def test(tmp_path):
"CIBW_ENVIRONMENT": """CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path""",
"CIBW_ENVIRONMENT_WINDOWS": f'''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$({python_echo} 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''',
},
single_python=True,
)
# also check that we got the right wheels built
expected_wheels = utils.expected_wheels("spam", "0.1.0")
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
@@ -92,10 +93,18 @@ def test_overridden_path(tmp_path, capfd):
assert len(os.listdir(output_dir)) == 0
captured = capfd.readouterr()
assert "python available on PATH doesn't match our installed instance" in captured.err
assert "python available on PATH doesn't match our installed instance" in captured.err.replace(
"venv", "installed"
)
@pytest.mark.parametrize("build_frontend", ["pip", "build"])
@pytest.mark.parametrize(
"build_frontend",
[
pytest.param("pip", marks=utils.skip_if_pyodide("No pip for pyodide")),
"build",
],
)
def test_overridden_pip_constraint(tmp_path, build_frontend):
"""
Verify that users can use PIP_CONSTRAINT to specify a specific version of
@@ -108,7 +117,7 @@ def test_overridden_pip_constraint(tmp_path, build_frontend):
setup_py_add=textwrap.dedent(
"""
import pytz
assert pytz.__version__ == "2022.4"
assert pytz.__version__ == "2022.4", f"{pytz.__version__!r} != '2022.4'"
"""
)
)
@@ -133,12 +142,12 @@ def test_overridden_pip_constraint(tmp_path, build_frontend):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp312-*",
"CIBW_BUILD_FRONTEND": build_frontend,
"PIP_CONSTRAINT": str(constraints_file),
"CIBW_ENVIRONMENT_LINUX": "PIP_CONSTRAINT=./constraints.txt",
},
single_python=True,
)
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp312" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
+11 -21
View File
@@ -6,6 +6,7 @@ import sys
import textwrap
from pathlib import Path
from tempfile import TemporaryDirectory
from test.test_projects.base import TestProject
from . import test_projects, utils
@@ -33,6 +34,8 @@ def cibuildwheel_from_sdist_run(sdist_path, add_env=None, config_file=None):
if add_env:
env.update(add_env)
env["CIBW_BUILD"] = "cp{}{}-*".format(*utils.SINGLE_PYTHON_VERSION)
with TemporaryDirectory() as tmp_output_dir:
subprocess.run(
[
@@ -73,15 +76,11 @@ def test_simple(tmp_path):
# build the wheels from sdist
actual_wheels = cibuildwheel_from_sdist_run(
sdist_path,
add_env={
"CIBW_BEFORE_BUILD": setup_py_assertion_cmd,
"CIBW_BUILD": "cp39-*",
},
sdist_path, add_env={"CIBW_BEFORE_BUILD": setup_py_assertion_cmd}
)
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp39" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
@@ -105,14 +104,10 @@ def test_external_config_file_argument(tmp_path, capfd):
)
# build the wheels from sdist
actual_wheels = cibuildwheel_from_sdist_run(
sdist_path,
add_env={"CIBW_BUILD": "cp39-*"},
config_file=str(config_file),
)
actual_wheels = cibuildwheel_from_sdist_run(sdist_path, config_file=str(config_file))
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp39" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
# check that before-all was run
@@ -136,13 +131,10 @@ def test_config_in_pyproject_toml(tmp_path, capfd):
sdist_path = make_sdist(project, sdist_dir)
# build the wheels from sdist
actual_wheels = cibuildwheel_from_sdist_run(
sdist_path,
add_env={"CIBW_BUILD": "cp39-*"},
)
actual_wheels = cibuildwheel_from_sdist_run(sdist_path)
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp39" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
# check that before-build was run
@@ -174,13 +166,11 @@ def test_internal_config_file_argument(tmp_path, capfd):
# build the wheels from sdist, referencing the config file inside
actual_wheels = cibuildwheel_from_sdist_run(
sdist_path,
add_env={"CIBW_BUILD": "cp39-*"},
config_file="{package}/wheel_build_config.toml",
sdist_path, config_file="{package}/wheel_build_config.toml"
)
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp39" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
# check that before-all was run
+22 -21
View File
@@ -14,7 +14,7 @@ ALL_MACOS_WHEELS = {
*utils.expected_wheels("spam", "0.1.0", machine_arch="arm64", include_universal2=True),
}
DEPLOYMENT_TARGET_TOO_LOW_WARNING = "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to a lower value"
DEPLOYMENT_TARGET_TOO_LOW_WARNING = "Bumping MACOSX_DEPLOYMENT_TARGET"
def get_xcode_version() -> tuple[int, int]:
@@ -42,13 +42,11 @@ def test_cross_compiled_build(tmp_path):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*",
"CIBW_ARCHS": "x86_64, universal2, arm64",
},
add_env={"CIBW_ARCHS": "x86_64, universal2, arm64"},
single_python=True,
)
expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w]
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -65,7 +63,7 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*" if build_universal2 else "*p39-*",
"CIBW_BUILD": "cp310-*" if build_universal2 else "*p310-*",
"CIBW_TEST_COMMAND": '''python -c "import platform; print('running tests on ' + platform.machine())"''',
"CIBW_ARCHS": "universal2" if build_universal2 else "x86_64 arm64",
"CIBW_BUILD_VERBOSITY": "3",
@@ -103,11 +101,11 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
)
if build_universal2:
expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w and "universal2" in w]
expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp310" in w and "universal2" in w]
else:
expected_wheels = [w for w in ALL_MACOS_WHEELS if "p39-" in w and "universal2" not in w]
expected_wheels = [w for w in ALL_MACOS_WHEELS if "p310-" in w and "universal2" not in w]
if platform_machine == "x86_64":
expected_wheels = [w for w in expected_wheels if not ("pp39" in w and "arm64" in w)]
expected_wheels = [w for w in expected_wheels if not ("pp310" in w and "arm64" in w)]
assert set(actual_wheels) == set(expected_wheels)
@@ -123,11 +121,11 @@ def test_deployment_target_warning_is_firing(tmp_path, capfd):
utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*",
"CIBW_ARCHS": "x86_64",
"MACOSX_DEPLOYMENT_TARGET": "10.8",
"CIBW_BUILD_VERBOSITY": "3",
},
single_python=True,
)
captured = capfd.readouterr()
@@ -149,11 +147,11 @@ def test_universal2_testing_on_x86_64(tmp_path, capfd, skip_arm64_test):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*",
"CIBW_TEST_COMMAND": '''python -c "import platform; print('running tests on ' + platform.machine())"''',
"CIBW_ARCHS": "universal2",
"CIBW_TEST_SKIP": "*_universal2:arm64" if skip_arm64_test else "",
},
single_python=True,
)
captured = capfd.readouterr()
@@ -168,12 +166,13 @@ def test_universal2_testing_on_x86_64(tmp_path, capfd, skip_arm64_test):
else:
assert warning_message in captured.err
expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w and "universal2" in w]
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w and "universal2" in w]
assert set(actual_wheels) == set(expected_wheels)
def test_universal2_testing_on_arm64(tmp_path, capfd):
def test_universal2_testing_on_arm64(build_frontend_env, tmp_path, capfd):
# cibuildwheel should test the universal2 wheel on both x86_64 and arm64, when run on arm64
if utils.platform != "macos":
pytest.skip("this test is only relevant to macos")
@@ -186,19 +185,21 @@ def test_universal2_testing_on_arm64(tmp_path, capfd):
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*",
"CIBW_ARCHS": "universal2",
# check that a native dependency is correctly installed, once per each testing arch
"CIBW_TEST_REQUIRES": "numpy",
"CIBW_TEST_COMMAND": '''python -c "import numpy, platform; print(f'running tests on {platform.machine()} with numpy {numpy.__version__}')"''',
"CIBW_TEST_REQUIRES": "--only-binary :all: pillow>=10.3", # pillow>=10.3 provides wheels for macOS 10.10, not 10.9
"CIBW_TEST_COMMAND": '''python -c "import PIL, platform; print(f'running tests on {platform.machine()} with pillow {PIL.__version__}')"''',
**build_frontend_env,
},
single_python=True,
)
captured = capfd.readouterr()
assert "running tests on arm64" in captured.out
assert "running tests on x86_64" in captured.out
assert "running tests on arm64 with pillow" in captured.out
assert "running tests on x86_64 with pillow" in captured.out
expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w and "universal2" in w]
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w and "universal2" in w]
assert set(actual_wheels) == set(expected_wheels)
+3 -2
View File
@@ -38,7 +38,7 @@ def test(musllinux_image, tmp_path):
# build the wheels
add_env = {
"CIBW_BUILD": "*-musllinux*",
"CIBW_SKIP": "*-manylinux*",
"CIBW_MUSLLINUX_X86_64_IMAGE": musllinux_image,
"CIBW_MUSLLINUX_I686_IMAGE": musllinux_image,
"CIBW_MUSLLINUX_AARCH64_IMAGE": musllinux_image,
@@ -46,11 +46,12 @@ def test(musllinux_image, tmp_path):
"CIBW_MUSLLINUX_S390X_IMAGE": musllinux_image,
}
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env)
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env, single_python=True)
expected_wheels = utils.expected_wheels(
"spam",
"0.1.0",
manylinux_versions=[],
musllinux_versions=[musllinux_image],
single_python=True,
)
assert set(actual_wheels) == set(expected_wheels)
+3 -4
View File
@@ -8,8 +8,8 @@ basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
"""
# Will fail if PEP 518 does work
import requests
assert requests.__version__ == "2.27.0", "Requests found but wrong version ({0})".format(requests.__version__)
import jmespath
assert jmespath.__version__ == "0.10.0", "'jmespath' found but wrong version ({0})".format(jmespath.__version__)
# Just making sure environment is still set
import os
@@ -24,8 +24,7 @@ basic_project.files["pyproject.toml"] = """
requires = [
"setuptools >= 42",
"setuptools_scm[toml]>=4.1.2",
"wheel",
"requests==2.27.0"
"jmespath==0.10.0"
]
build-backend = "setuptools.build_meta"
+6 -1
View File
@@ -42,15 +42,20 @@ PyMODINIT_FUNC PyInit_spam(void)
"""
SETUP_PY_TEMPLATE = r"""
import os
import sys
from setuptools import setup, Extension
{{ setup_py_add }}
libraries = []
if sys.platform.startswith('linux'):
# Emscripten fails if you pass -lc...
# See: https://github.com/emscripten-core/emscripten/issues/16680
if sys.platform.startswith('linux') and "emscripten" not in os.environ.get("_PYTHON_HOST_PLATFORM", ""):
libraries.extend(['m', 'c'])
setup(
ext_modules=[Extension(
'spam',
+4 -2
View File
@@ -1,10 +1,11 @@
from __future__ import annotations
import subprocess
from test import test_projects
import pytest
from test import test_projects
from . import utils
pure_python_project = test_projects.TestProject()
@@ -30,10 +31,11 @@ def test(tmp_path, capfd):
project_dir = tmp_path / "project"
pure_python_project.generate(project_dir)
with pytest.raises(subprocess.CalledProcessError):
with pytest.raises(subprocess.CalledProcessError) as exc_info:
print("produced wheels:", utils.cibuildwheel_run(project_dir))
captured = capfd.readouterr()
print("out", captured.out)
print("err", captured.err)
assert exc_info.value.returncode == 5
assert "Build failed because a pure Python wheel was generated" in captured.err
+2 -3
View File
@@ -45,13 +45,12 @@ def test(capfd, tmp_path):
add_env={
"CIBW_BEFORE_BUILD": "python {project}/bin/before_build.py",
"CIBW_TEST_COMMAND": "python {package}/test/run_tests.py",
# this shouldn't depend on the version of python, so build only CPython 3.10
"CIBW_BUILD": "cp310-*",
},
single_python=True,
)
# check that the expected wheels are produced
expected_wheels = [w for w in utils.expected_wheels("spam", "0.1.0") if "cp310" in w]
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
captured = capfd.readouterr()
+8 -6
View File
@@ -58,6 +58,7 @@ class TestSpam(TestCase):
self.assertTrue(path_contains(sys.prefix, sys.executable))
self.assertTrue(path_contains(sys.prefix, spam.__file__))
self.assertIn("VIRTUAL_ENV", os.environ)
def test_uname(self):
if platform.system() == "Windows":
@@ -66,7 +67,7 @@ class TestSpam(TestCase):
# See #336 for more info.
bits = struct.calcsize("P") * 8
if bits == 32:
self.assertEqual(platform.machine(), "i686")
self.assertIn(platform.machine(), ["i686", "wasm32"])
'''
@@ -81,7 +82,7 @@ def test(tmp_path):
"CIBW_TEST_REQUIRES": "pytest",
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
"CIBW_TEST_COMMAND": "false || pytest {project}/test",
"CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test",
"CIBW_TEST_COMMAND_WINDOWS": "COLOR 00 || pytest {project}/test",
},
)
@@ -102,13 +103,14 @@ def test_extras_require(tmp_path):
"CIBW_TEST_EXTRAS": "test",
# the 'false ||' bit is to ensure this command runs in a shell on
# mac/linux.
"CIBW_TEST_COMMAND": "false || pytest {project}/test",
"CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test",
"CIBW_TEST_COMMAND_WINDOWS": "COLOR 00 || pytest {project}/test",
},
single_python=True,
)
# also check that we got the right wheels
expected_wheels = utils.expected_wheels("spam", "0.1.0")
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
assert set(actual_wheels) == set(expected_wheels)
@@ -133,8 +135,8 @@ def test_failing_test(tmp_path):
project_dir,
output_dir=output_dir,
add_env={
"CIBW_TEST_REQUIRES": "nose",
"CIBW_TEST_COMMAND": "nosetests {project}/test",
"CIBW_TEST_REQUIRES": "pytest",
"CIBW_TEST_COMMAND": f"{utils.invoke_pytest()} {{project}}/test",
# manylinux1 has a version of bash that's been shown to have
# problems with this, so let's check that.
"CIBW_MANYLINUX_I686_IMAGE": "manylinux1",
+3 -6
View File
@@ -18,17 +18,14 @@ def test_wheel_tag_is_correct_when_using_macosx_deployment_target(tmp_path):
deployment_target = "10.11"
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp39-*",
"MACOSX_DEPLOYMENT_TARGET": deployment_target,
},
add_env={"MACOSX_DEPLOYMENT_TARGET": deployment_target},
single_python=True,
)
# check that the expected wheels are produced
expected_wheels = utils.expected_wheels(
"spam", "0.1.0", macosx_deployment_target=deployment_target
"spam", "0.1.0", macosx_deployment_target=deployment_target, single_python=True
)
expected_wheels = [w for w in expected_wheels if "cp39" in w]
print("actual_wheels", actual_wheels)
print("expected_wheels", expected_wheels)
+3 -6
View File
@@ -60,16 +60,13 @@ def test_wheel_tag_is_correct_when_using_windows_cross_compile(tmp_path, use_pyp
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_BUILD": "cp310-*",
},
add_args=["--archs", "ARM64"],
single_python=True,
)
# check that the expected wheels are produced
expected_wheels = [
"spam-0.1.0-cp310-cp310-win_arm64.whl",
]
tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [f"spam-0.1.0-{tag}-{tag}-win_arm64.whl"]
print("actual_wheels", actual_wheels)
print("expected_wheels", expected_wheels)
+77 -27
View File
@@ -11,18 +11,26 @@ import platform as pm
import subprocess
import sys
from tempfile import TemporaryDirectory
from typing import Final
import pytest
from cibuildwheel.architecture import Architecture
from cibuildwheel.util import CIBW_CACHE_PATH
platform: str
EMULATED_ARCHS: Final[list[str]] = sorted(
arch.value for arch in (Architecture.all_archs("linux") - Architecture.auto_archs("linux"))
)
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 12)
if "CIBW_PLATFORM" in os.environ:
platform = os.environ["CIBW_PLATFORM"]
platform = os.environ.get("CIBW_PLATFORM", "")
if platform:
pass
elif sys.platform.startswith("linux"):
platform = "linux"
elif sys.platform.startswith("darwin"):
platform = "macos"
elif sys.platform in ["win32", "cygwin"]:
elif sys.platform.startswith(("win32", "cygwin")):
platform = "windows"
else:
msg = f"Unsupported platform {sys.platform!r}"
@@ -67,7 +75,13 @@ def _update_pip_cache_dir(env: dict[str, str]) -> None:
def cibuildwheel_run(
project_path, package_dir=".", env=None, add_env=None, output_dir=None, add_args=None
project_path,
package_dir=".",
env=None,
add_env=None,
output_dir=None,
add_args=None,
single_python=False,
):
"""
Runs cibuildwheel as a subprocess, building the project at project_path.
@@ -99,6 +113,9 @@ def cibuildwheel_run(
env.setdefault("CIBW_FREE_THREADED_SUPPORT", "1")
if single_python:
env["CIBW_BUILD"] = "cp{}{}-*".format(*SINGLE_PYTHON_VERSION)
with TemporaryDirectory() as tmp_output_dir:
subprocess.run(
[
@@ -119,17 +136,11 @@ def cibuildwheel_run(
return wheels
def _get_arm64_macosx_deployment_target(macosx_deployment_target: str) -> str:
def _floor_macosx(*args: str) -> str:
"""
The first version of macOS that supports arm is 11.0. So the wheel tag
cannot contain an earlier deployment target, even if
MACOSX_DEPLOYMENT_TARGET sets it.
Make sure a deployment target is not less than some value.
"""
version_tuple = tuple(map(int, macosx_deployment_target.split(".")))
if version_tuple <= (11, 0):
return "11.0"
else:
return macosx_deployment_target
return max(args, key=lambda x: tuple(map(int, x.split("."))))
def expected_wheels(
@@ -141,6 +152,8 @@ def expected_wheels(
machine_arch=None,
python_abi_tags=None,
include_universal2=False,
single_python=False,
single_arch=False,
):
"""
Returns a list of expected wheels from a run of cibuildwheel.
@@ -170,6 +183,8 @@ def expected_wheels(
if musllinux_versions is None:
musllinux_versions = ["musllinux_1_2"]
if platform == "pyodide" and python_abi_tags is None:
python_abi_tags = ["cp312-cp312"]
if python_abi_tags is None:
python_abi_tags = [
"cp36-cp36m",
@@ -180,9 +195,8 @@ def expected_wheels(
"cp311-cp311",
"cp312-cp312",
"cp313-cp313",
"cp313-cp313t",
]
if platform != "macos":
python_abi_tags.append("cp313-cp313t")
if machine_arch in ["x86_64", "AMD64", "x86", "aarch64"]:
python_abi_tags += [
@@ -201,20 +215,37 @@ def expected_wheels(
"cp311-cp311",
"cp312-cp312",
"cp313-cp313",
"cp313-cp313t",
"pp38-pypy38_pp73",
"pp39-pypy39_pp73",
"pp310-pypy310_pp73",
]
if single_python:
python_tag = "cp{}{}-".format(*SINGLE_PYTHON_VERSION)
python_abi_tags = [
next(
tag
for tag in python_abi_tags
if tag.startswith(python_tag) and not tag.endswith("t")
)
]
wheels = []
if platform == "pyodide":
assert len(python_abi_tags) == 1
python_abi_tag = python_abi_tags[0]
platform_tag = "pyodide_2024_0_wasm32"
return [f"{package_name}-{package_version}-{python_abi_tag}-{platform_tag}.whl"]
for python_abi_tag in python_abi_tags:
platform_tags = []
if platform == "linux":
architectures = [arch_name_for_linux(machine_arch)]
if machine_arch == "x86_64":
if machine_arch == "x86_64" and not single_arch:
architectures.append("i686")
if len(manylinux_versions) > 0:
@@ -243,19 +274,27 @@ def expected_wheels(
platform_tags = ["win32", "win_amd64"]
elif platform == "macos":
if machine_arch == "arm64":
arm64_macosx_deployment_target = _get_arm64_macosx_deployment_target(
macosx_deployment_target
)
platform_tags = [f'macosx_{arm64_macosx_deployment_target.replace(".", "_")}_arm64']
if python_abi_tag.startswith("pp"):
if python_abi_tag.startswith(("pp37", "pp38")):
min_macosx = macosx_deployment_target
else:
min_macosx = _floor_macosx(macosx_deployment_target, "10.15")
elif python_abi_tag.startswith("cp"):
if python_abi_tag.startswith(("cp36", "cp37", "cp38", "cp39", "cp310", "cp311")):
min_macosx = macosx_deployment_target
else:
min_macosx = _floor_macosx(macosx_deployment_target, "10.13")
else:
platform_tags = [f'macosx_{macosx_deployment_target.replace(".", "_")}_x86_64']
min_macosx = macosx_deployment_target
if machine_arch == "arm64":
arm64_macosx = _floor_macosx(min_macosx, "11.0")
platform_tags = [f'macosx_{arm64_macosx.replace(".", "_")}_arm64']
else:
platform_tags = [f'macosx_{min_macosx.replace(".", "_")}_x86_64']
if include_universal2:
platform_tags.append(
f'macosx_{macosx_deployment_target.replace(".", "_")}_universal2',
)
platform_tags.append(f'macosx_{min_macosx.replace(".", "_")}_universal2')
else:
msg = f"Unsupported platform {platform!r}"
raise Exception(msg)
@@ -278,6 +317,17 @@ def get_macos_version():
return tuple(map(int, version_str.split(".")[:2]))
def skip_if_pyodide(reason: str):
return pytest.mark.skipif(platform == "pyodide", reason=reason)
def invoke_pytest() -> str:
# see https://github.com/pyodide/pyodide/issues/4802
if platform == "pyodide" and sys.platform.startswith("darwin"):
return "python -m pytest"
return "pytest"
def arch_name_for_linux(arch: str):
"""
Archs have different names on different platforms, but it's useful to be
+2 -2
View File
@@ -13,7 +13,7 @@ def test_build():
assert build_selector("cp310-manylinux_x86_64")
assert build_selector("cp311-manylinux_x86_64")
assert build_selector("cp312-manylinux_x86_64")
assert not build_selector("cp313-manylinux_x86_64")
assert build_selector("cp313-manylinux_x86_64")
assert build_selector("pp36-manylinux_x86_64")
assert build_selector("pp37-manylinux_x86_64")
assert build_selector("cp36-manylinux_i686")
@@ -34,7 +34,7 @@ def test_build():
assert build_selector("cp310-win_amd64")
assert build_selector("cp311-win_amd64")
assert build_selector("cp312-win_amd64")
assert not build_selector("cp313-win_amd64")
assert build_selector("cp313-win_amd64")
assert not build_selector("pp36-win_amd64")
assert not build_selector("pp37-win_amd64")
+1 -1
View File
@@ -19,7 +19,7 @@ def pytest_addoption(parser):
)
@pytest.fixture()
@pytest.fixture
def fake_package_dir(tmp_path, monkeypatch):
"""
Monkey-patch enough for the main() function to run
+92
View File
@@ -0,0 +1,92 @@
import contextlib
import sys
from pathlib import Path
from typing import Dict
import pytest
import setuptools._distutils.util
from cibuildwheel.errors import FatalError
from cibuildwheel.util import CIProvider, detect_ci_provider
from cibuildwheel.windows import PythonConfiguration, setup_setuptools_cross_compile
# monkeypatching os.name is too flaky. E.g. It works on my machine, but fails in pipeline
if not sys.platform.startswith("win"):
pytest.skip("Windows-only tests", allow_module_level=True)
@contextlib.contextmanager
def patched_environment(monkeypatch: pytest.MonkeyPatch, environment: Dict[str, str]):
with monkeypatch.context() as mp:
for envvar, val in environment.items():
mp.setenv(name=envvar, value=val)
yield
def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
arch = "32"
environment: Dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment):
target_platform = setuptools._distutils.util.get_platform()
assert environment["VSCMD_ARG_TGT_ARCH"] == "x86"
assert target_platform == "win32"
def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
arch = "64"
environment: Dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment):
target_platform = setuptools._distutils.util.get_platform()
assert environment["VSCMD_ARG_TGT_ARCH"] == "x64"
assert target_platform == "win-amd64"
@pytest.mark.skipif(
detect_ci_provider() == CIProvider.azure_pipelines, reason="arm64 not recognised on azure"
)
def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
arch = "ARM64"
environment: Dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment):
target_platform = setuptools._distutils.util.get_platform()
assert environment["VSCMD_ARG_TGT_ARCH"] == "arm64"
assert target_platform == "win-arm64"
def test_env_set(tmp_path: Path):
arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": "x64"}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None)
with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"):
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": ""}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment):
target_platform = setuptools._distutils.util.get_platform()
assert environment["VSCMD_ARG_TGT_ARCH"] == "x86"
assert target_platform == "win32"
+3 -2
View File
@@ -71,9 +71,10 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
"cp36-manylinux_x86_64",
"cp311-manylinux_x86_64",
"cp312-manylinux_x86_64",
"cp313-manylinux_x86_64",
]
assert before_alls(build_steps[0]) == [""] * 3
assert container_engines(build_steps[0]) == [default_container_engine] * 3
assert before_alls(build_steps[0]) == [""] * 4
assert container_engines(build_steps[0]) == [default_container_engine] * 4
assert build_steps[1].container_image == "other_container_image"
assert identifiers(build_steps[1]) == ["cp37-manylinux_x86_64", "cp38-manylinux_x86_64"]

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