diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb2ebe8a..c261259a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,9 @@ jobs: needs: [dist] runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' + environment: pypi + permissions: + id-token: write steps: - uses: actions/download-artifact@v3 @@ -34,7 +37,4 @@ jobs: name: artifact path: dist - - uses: pypa/gh-action-pypi-publish@v1.6.4 - with: - user: __token__ - password: ${{ secrets.pypi_password }} + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36ed2511..aa07602e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,9 @@ jobs: matrix: os: [ubuntu-20.04, windows-latest, macos-11] python_version: ['3.11'] + include: + - os: ubuntu-22.04 + python_version: '3.7' timeout-minutes: 180 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 414c460a..7ae7b9b9 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -22,9 +22,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: wntrblm/nox@2022.11.21 + - uses: wntrblm/nox@2023.04.22 with: - python-versions: "3.6, 3.7, 3.8, 3.9, 3.10, 3.11" + python-versions: "3.7, 3.8, 3.9, 3.10, 3.11, 3.12-dev" - name: "Run update: dependencies" run: nox --force-color -s update_constraints @@ -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@v4 + uses: peter-evans/create-pull-request@v5 with: commit-message: Update dependencies title: '[Bot] Update dependencies' diff --git a/.github/workflows/update-major-minor-tag.yml b/.github/workflows/update-major-minor-tag.yml new file mode 100644 index 00000000..d25466f0 --- /dev/null +++ b/.github/workflows/update-major-minor-tag.yml @@ -0,0 +1,30 @@ +name: Update the vX.Y tag + +on: + release: + types: [released] + workflow_dispatch: + inputs: + TAG_NAME: + description: 'Tag name that the major.minor tag will point to' + required: true + +env: + TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} + +jobs: + update_tag: + name: Update the major.minor tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes + environment: + name: releaseNewActionVersion + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + + - name: Update the ${{ env.TAG_NAME }} tag + id: update-major-minor-tag + uses: joerick/update-vX.Y-tag-action@v1.0 + with: + source-tag: ${{ env.TAG_NAME }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2acbdf4..7bbc80ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,24 +14,24 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.243 + rev: v0.0.272 hooks: - id: ruff - args: ["--fix"] + args: ["--fix", "--show-fixes"] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 + rev: v2.3.0 hooks: - id: setup-cfg-fmt args: [--include-version-classifiers, --max-py-version=3.11] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.0 + rev: v1.3.0 hooks: - id: mypy name: mypy 3.7 on cibuildwheel/ @@ -46,20 +46,19 @@ repos: - tomli_w - types-certifi - types-click - - types-dataclasses - types-jinja2 - types-pyyaml - types-requests - bracex - - dataclasses + - markdown-it-py<3 - id: mypy - name: mypy 3.10 + name: mypy 3.11 exclude: ^cibuildwheel/resources/.*py$ - args: ["--python-version=3.10"] + args: ["--python-version=3.11"] additional_dependencies: *mypy-dependencies - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.9.0.2 + rev: v0.9.0.5 hooks: - id: shellcheck @@ -79,7 +78,7 @@ repos: files: ^docs/changelog.md$ - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 + rev: v2.2.5 hooks: - id: codespell args: ["-L", "sur"] diff --git a/.travis.yml b/.travis.yml index 4256a69b..cf45e94e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,8 +37,6 @@ jobs: language: shell before_install: - choco upgrade python3 -y --version 3.7.9 --limit-output - # Update root certificates to fix SSL error; see http://www.chawn.com/RootCerts.htm - - powershell "md C:\temp\certs; CertUtil -generateSSTFromWU C:\temp\certs\RootStore.sst; Get-ChildItem -Path C:\\temp\certs\Rootstore.sst | Import-Certificate -CertStoreLocation Cert:\\LocalMachine\\Root\\ | out-null" env: - PYTHON=C:\\Python37\\python diff --git a/CI.md b/CI.md index 1fa1c510..98d36c73 100644 --- a/CI.md +++ b/CI.md @@ -1,11 +1,11 @@ This is a summary of the Python versions and platforms covered by the different CI platforms: -| | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | -|---------|-----------------------|--------------------------|----------|-----------|----------------| -| Linux | AppVeyor¹ / Travis CI | Azure Pipelines / GitLab | CircleCI | Cirrus CI | GitHub Actions | -| macOS | AppVeyor¹ / Travis CI | Azure Pipelines | CircleCI | Cirrus CI | GitHub Actions | -| Windows | AppVeyor¹ / Travis CI | Azure Pipelines | | Cirrus CI | GitHub Actions | +| | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | +|---------|-----------|---------------------------------------|-----------|-----------|----------------| +| Linux | Travis CI | AppVeyor¹ / Azure Pipelines / GitLab¹ | CircleCI¹ | Cirrus CI | GitHub Actions | +| macOS | Travis CI | AppVeyor¹ / Azure Pipelines | CircleCI¹ | Cirrus CI | GitHub Actions | +| Windows | Travis CI | AppVeyor¹ / Azure Pipelines / GitLab¹ | | Cirrus CI | GitHub Actions | -> ¹ AppVeyor only runs the "basic" test to reduce load. +> ¹ Runs a reduced set of tests to reduce CI load Non-x86 architectures are covered on Travis CI using Python 3.7. diff --git a/README.md b/README.md index c46327c0..5c28cc18 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ What does it do? | CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅³ | ✅ | ✅ | ✅ | ✅ | | CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | | CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | +| CPython 3.12⁵ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | | 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 | @@ -39,6 +40,7 @@ What does it do? ² Windows arm64 support is experimental.
³ Alpine 3.14 and very briefly 3.15's default python3 [was not able to load](https://github.com/pypa/cibuildwheel/issues/934) musllinux wheels. This has been fixed; please upgrade the python package if using Alpine from before the fix.
⁴ Cross-compilation not supported with PyPy - to build these wheels you need to run cibuildwheel on an Apple Silicon machine.
+⁵ CPython 3.12 is available using the [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.readthedocs.io/en/stable/options/#prerelease-pythons) option.
- 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 @@ -94,7 +96,7 @@ jobs: - uses: actions/setup-python@v3 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.12.0 + run: python -m pip install cibuildwheel==2.13.1 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse @@ -158,26 +160,26 @@ Here are some repos that use cibuildwheel. | Name | CI | OS | Notes | |-----------------------------------|----|----|:------| | [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. | | [Tornado][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed. | -| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. | | [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 | -| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS | | [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. | +| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS | | [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. | -| [pydantic][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Data parsing and validation using Python type hints | +| [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python | [scikit-learn]: https://github.com/scikit-learn/scikit-learn +[pytorch-fairseq]: https://github.com/pytorch/fairseq [NumPy]: https://github.com/numpy/numpy [Tornado]: https://github.com/tornadoweb/tornado -[pytorch-fairseq]: https://github.com/pytorch/fairseq [Matplotlib]: https://github.com/matplotlib/matplotlib [NCNN]: https://github.com/Tencent/ncnn -[Kivy]: https://github.com/kivy/kivy [Prophet]: https://github.com/facebook/prophet +[Kivy]: https://github.com/kivy/kivy [MyPy]: https://github.com/mypyc/mypy_mypyc-wheels -[pydantic]: https://github.com/samuelcolvin/pydantic +[MemRay]: https://github.com/bloomberg/memray [appveyor icon]: docs/data/readme_icons/appveyor.svg [github icon]: docs/data/readme_icons/github.svg @@ -210,47 +212,42 @@ Changelog -### v2.12.0 +### v2.13.1 -_16 Jan 2023_ +_10 June 2023_ -- ✨ Adds support for PyPy arm64 wheels. This means that you can build PyPy wheels for Apple Silicon machines. Cross-compilation is not supported for these wheels, so you'll have to build on an Apple Silicon machine. (#1372) -- 🛠 Pinned version updates, including PyPy to v7.3.11 and setuptools to 66.0.0. +- 🛠 Updates the prerelease CPython 3.12 version to 3.12.0b2. (#1516) +- 🛠 Adds a moving `v.` tag for use in GitHub Actions workflow files. If you use this, you'll get the latest patch release within a minor version. Additionally, Dependabot won't send you PRs for patch releases. (#1517) -### v2.11.4 +### v2.13.0 -_24 Dec 2022_ +_28 May 2023_ -- 🐛 Fix a bug that caused missing wheels on Windows when a test was skipped using CIBW_TEST_SKIP (#1377) -- 🛠 Updates CPython 3.11 to 3.11.1 (#1371) -- 🛠 Updates PyPy to 7.3.10, except on macOS which remains on 7.3.9 due to a bug on that platform. (#1371) -- 📚 Added a reference to abi3audit to the docs (#1347) +- ✨ Adds CPython 3.12 support, under the prerelease flag [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.readthedocs.io/en/stable/options/#prerelease-pythons). This version of cibuildwheel uses 3.12.0b1. -### v2.11.3 + 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.12 will be available in cibuildwheel without the flag. (#1507) -_5 Dec 2022_ +- ✨ Adds the ability to pass arguments to the container engine when the container is created, using the [CIBW_CONTAINER_ENGINE](https://cibuildwheel.readthedocs.io/en/stable/options/#container-engine) option. (#1499) -- ✨ Improves the 'build options' log output that's printed at the start of each run (#1352) -- ✨ Added a friendly error message to a common misconfiguration of the `CIBW_TEST_COMMAND` option - not specifying path using the `{project}` placeholder (#1336) -- 🛠 The GitHub Action now uses Powershell on Windows to avoid occasional incompabilities with bash (#1346) +### v2.12.3 -### v2.11.2 +_19 April 2023_ -_26 October 2022_ +- 🐛 Fix an import error when running on Python 3.7. (#1479) -- 🛠 Updates CPython 3.11 to 3.11.0 - final release (#1327) -- 🛠 Simplify the default macOS repair command (#1322) -- 🛠 Fix the default `MACOSX_DEPLOYMENT_TARGET` on arm64 (#1312) -- 🛠 Hide irrelevant pip warnings on linux (#1311) -- 🐛 Fix a bug that caused the stdout and stderr of commands in containers to be in the wrong order Previously, stdout could appear after stderr. (#1324) -- 📚 Added [a FAQ entry](https://cibuildwheel.readthedocs.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64) describing how to perform native builds of CPython 3.8 wheels on Apple Silicon. (#1323) -- 📚 Other docs improvements +### v2.12.2 -### v2.11.1 +_18 April 2023_ -_13 October 2022_ +- 🐛 Fix a bug that caused an extra empty config-setting to be passed to the backend when CIBW_BUILD_FRONTEND is set to `build`. (#1474) +- 🐛 Fix a crash that occurred when overwriting an existing wheel on Windows. (#1464) +- 🛠 Pinned version updates, including CPython 3.10.11, 3.11.3, pip 23.1 and wheel 0.40.0. -- 🛠 Updates to the latest manylinux images, and updates CPython 3.10 to 3.10.8. +### v2.12.1 + +_11 March 2023_ + +- 🐛 Fix a bug that prevented the use of CIBW_CONFIG_SETTINGS with the 'pip' build backend. (#1430) diff --git a/appveyor.yml b/appveyor.yml index a832cc7c..4f12790d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,18 +1,18 @@ environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu - APPVEYOR_JOB_NAME: "python37-x64-ubuntu" + APPVEYOR_JOB_NAME: "python38-x64-ubuntu" - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - APPVEYOR_JOB_NAME: "python37-x64-vs2015" + APPVEYOR_JOB_NAME: "python38-x64-vs2015" - APPVEYOR_BUILD_WORKER_IMAGE: macos - APPVEYOR_JOB_NAME: "python37-x64-macos" + APPVEYOR_JOB_NAME: "python38-x64-macos" -stack: python 3.7 +stack: python 3.8 build: off init: -- cmd: set PATH=C:\Python37;C:\Python37\Scripts;%PATH% +- 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-'))) { diff --git a/bin/inspect_all_known_projects.py b/bin/inspect_all_known_projects.py index 3f91b2b5..a2c68a6b 100755 --- a/bin/inspect_all_known_projects.py +++ b/bin/inspect_all_known_projects.py @@ -15,8 +15,8 @@ the results without the `--online` setting. from __future__ import annotations import ast +from collections.abc import Iterable, Iterator from pathlib import Path -from typing import Iterator import click import yaml @@ -97,7 +97,7 @@ class MaybeRemote: with open(filename, "w") as f: yaml.safe_dump(self.contents, f, default_flow_style=False) - def on_each(self, repos: list[str]) -> Iterator[tuple[str, str, str | None]]: + def on_each(self, repos: Iterable[str]) -> Iterator[tuple[str, str, str | None]]: for repo in repos: print(f"[bold]{repo}:") for filename in sorted(self.contents, reverse=True): diff --git a/bin/projects.py b/bin/projects.py old mode 100644 new mode 100755 index 6b577189..546a10c8 --- a/bin/projects.py +++ b/bin/projects.py @@ -16,6 +16,7 @@ import functools import textwrap import urllib.request import xml.dom.minidom +from collections.abc import Iterable, Mapping, Sequence from datetime import datetime from io import StringIO from pathlib import Path @@ -42,7 +43,7 @@ ICONS = ( class Project: NAME: int = 0 - def __init__(self, config: dict[str, Any], github: Github | None = None): + def __init__(self, config: Mapping[str, Any], github: Github | None = None): try: self.name: str = config["name"] self.gh: str = config["gh"] @@ -149,7 +150,7 @@ def path_for_icon(icon_name: str, relative_to: Path | None = None) -> Path: def get_projects( - config: list[dict[str, Any]], + config: Iterable[Mapping[str, Any]], *, online: bool = True, auth: str | None = None, @@ -163,7 +164,7 @@ def get_projects( return sorted((Project(item, github) for item in config), reverse=online) -def render_projects(projects: list[Project], *, dest_path: Path, include_info: bool = True): +def render_projects(projects: Sequence[Project], *, dest_path: Path, include_info: bool = True): io = StringIO() print = functools.partial(builtins.print, file=io) @@ -191,7 +192,7 @@ def render_projects(projects: list[Project], *, dest_path: Path, include_info: b def insert_projects_table( file: Path, *, - projects: list[Project], + projects: Sequence[Project], input_filename: str, include_info: bool = True, ): diff --git a/bin/update_how_it_works_image.py b/bin/update_how_it_works_image.py old mode 100644 new mode 100755 diff --git a/bin/update_pythons.py b/bin/update_pythons.py index 930b13df..e57a7173 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -5,26 +5,21 @@ from __future__ import annotations import copy import difflib import logging -import sys +from collections.abc import Mapping, MutableMapping from pathlib import Path from typing import Any, Union import click import requests import rich - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib - from packaging.specifiers import Specifier from packaging.version import Version from rich.logging import RichHandler from rich.syntax import Syntax +from cibuildwheel._compat import tomllib +from cibuildwheel._compat.typing import Final, Literal, TypedDict from cibuildwheel.extra import dump_python_configurations -from cibuildwheel.typing import Final, Literal, TypedDict log = logging.getLogger("cibw") @@ -93,7 +88,7 @@ class WindowsVersions: unsorted_versions = spec.filter(self.version_dict) versions = sorted(unsorted_versions, reverse=True) - log.debug(f"Windows {self.arch} {spec} has {', '.join(str(v) for v in versions)}") + log.debug("Windows %s %s has %s", self.arch, spec, ", ".join(str(v) for v in versions)) if not versions: return None @@ -124,7 +119,7 @@ class PyPyVersions: ] self.arch = arch_str - def get_arch_file(self, release: dict[str, Any]) -> str: + def get_arch_file(self, release: Mapping[str, Any]) -> str: urls: list[str] = [ rf["download_url"] for rf in release["files"] @@ -250,11 +245,11 @@ class AllVersions: self.macos_pypy = PyPyVersions("64") self.macos_pypy_arm64 = PyPyVersions("ARM64") - def update_config(self, config: dict[str, str]) -> None: + def update_config(self, config: MutableMapping[str, str]) -> None: identifier = config["identifier"] version = Version(config["version"]) spec = Specifier(f"=={version.major}.{version.minor}.*") - log.info(f"Reading in '{identifier}' -> {spec} @ {version}") + log.info("Reading in %r -> %s @ %s", str(identifier), spec, version) orig_config = copy.copy(config) config_update: AnyConfig | None = None @@ -282,7 +277,7 @@ class AllVersions: config.update(**config_update) if config != orig_config: - log.info(f" Updated {orig_config} to {config}") + log.info(" Updated %s to %s", orig_config, config) @click.command() diff --git a/bin/update_virtualenv.py b/bin/update_virtualenv.py index ef1fec80..c4c70f29 100755 --- a/bin/update_virtualenv.py +++ b/bin/update_virtualenv.py @@ -5,23 +5,17 @@ from __future__ import annotations import difflib import logging import subprocess -import sys from dataclasses import dataclass from pathlib import Path import click import rich - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib - from packaging.version import InvalidVersion, Version from rich.logging import RichHandler from rich.syntax import Syntax -from cibuildwheel.typing import Final +from cibuildwheel._compat import tomllib +from cibuildwheel._compat.typing import Final log = logging.getLogger("cibw") @@ -54,14 +48,18 @@ def git_ls_remote_versions(url) -> list[VersionTuple]: try: version = Version(version_string) if version.is_devrelease: - log.info(f"Ignoring development release '{version}'") + log.info("Ignoring development release %r", str(version)) continue if version.is_prerelease: - log.info(f"Ignoring pre-release '{version}'") + log.info("Ignoring pre-release %r", str(version)) + continue + # Do not upgrade past 20.22.0 to keep python 3.6 compat + if version >= Version("20.22.0"): + log.info("Ignoring %r which is not compatible with python 3.6", str(version)) continue versions.append(VersionTuple(version, version_string)) except InvalidVersion: - log.warning(f"Ignoring ref '{ref}'") + log.warning("Ignoring ref %r", ref) versions.sort(reverse=True) return versions diff --git a/cibuildwheel/__init__.py b/cibuildwheel/__init__.py index 4a09ed48..33607129 100644 --- a/cibuildwheel/__init__.py +++ b/cibuildwheel/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "2.12.0" +__version__ = "2.13.1" diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 1ff447aa..0575f4e1 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -7,6 +7,7 @@ import sys import tarfile import textwrap import typing +from collections.abc import Iterable, Sequence, Set from pathlib import Path from tempfile import mkdtemp @@ -15,10 +16,11 @@ import cibuildwheel.linux import cibuildwheel.macos import cibuildwheel.util import cibuildwheel.windows +from cibuildwheel._compat.typing import Protocol, assert_never from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.logger import log from cibuildwheel.options import CommandLineArguments, Options, compute_options -from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never +from cibuildwheel.typing import PLATFORMS, GenericPythonConfiguration, PlatformName from cibuildwheel.util import ( CIBW_CACHE_PATH, BuildSelector, @@ -167,70 +169,102 @@ def main() -> None: log.warning(f"Can't delete temporary folder '{temp_dir}'") -def build_in_directory(args: CommandLineArguments) -> None: +def _compute_platform_only(only: str) -> PlatformName: + if "linux_" in only: + return "linux" + if "macosx_" in only: + 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) + + +def _compute_platform_ci() -> PlatformName: + if detect_ci_provider() is None: + print( + textwrap.dedent( + """ + cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server; + Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI + are supported. You can run on your development machine or other CI providers + using the --platform argument. Check --help output for more information. + """ + ), + file=sys.stderr, + ) + sys.exit(2) + if sys.platform.startswith("linux"): + return "linux" + elif sys.platform == "darwin": + return "macos" + elif sys.platform == "win32": + return "windows" + else: + print( + 'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run ' + "cibuildwheel using the --platform argument. Check --help output for more information.", + file=sys.stderr, + ) + sys.exit(2) + + +def _compute_platform(args: CommandLineArguments) -> PlatformName: platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto") - platform: PlatformName + + 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) + 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) + + if platform_option_value not in PLATFORMS | {"auto"}: + print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr) + sys.exit(2) if args.only: - if "linux_" in args.only: - platform = "linux" - elif "macosx_" in args.only: - platform = "macos" - elif "win_" in args.only or "win32" in args.only: - platform = "windows" - else: - print( - f"Invalid --only='{args.only}', must be a build selector with a known platform", - file=sys.stderr, - ) - sys.exit(2) - if args.platform is not None: - print( - "--platform cannot be specified with --only, it is computed from --only", - file=sys.stderr, - ) - sys.exit(2) - if args.archs is not None: - print( - "--arch cannot be specified with --only, it is computed from --only", - file=sys.stderr, - ) - sys.exit(2) + return _compute_platform_only(args.only) elif platform_option_value != "auto": - if platform_option_value not in PLATFORMS: - print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr) - sys.exit(2) + return typing.cast(PlatformName, platform_option_value) - platform = typing.cast(PlatformName, platform_option_value) - else: - ci_provider = detect_ci_provider() - if ci_provider is None: - print( - textwrap.dedent( - """ - cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server; - Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI - are supported. You can run on your development machine or other CI providers - using the --platform argument. Check --help output for more information. - """ - ), - file=sys.stderr, - ) - sys.exit(2) - if sys.platform.startswith("linux"): - platform = "linux" - elif sys.platform == "darwin": - platform = "macos" - elif sys.platform == "win32": - platform = "windows" - else: - print( - 'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run ' - "cibuildwheel using the --platform argument. Check --help output for more information.", - file=sys.stderr, - ) - sys.exit(2) + return _compute_platform_ci() + +class PlatformModule(Protocol): + # note that as per PEP544, the self argument is ignored when the protocol + # is applied to a module + def get_python_configurations( + self, build_selector: BuildSelector, architectures: Set[Architecture] + ) -> Sequence[GenericPythonConfiguration]: + ... + + def build(self, options: Options, tmp_path: Path) -> None: + ... + + +# pylint: disable-next=inconsistent-return-statements +def get_platform_module(platform: PlatformName) -> PlatformModule: + if platform == "linux": + return cibuildwheel.linux + if platform == "windows": + return cibuildwheel.windows + if platform == "macos": + return cibuildwheel.macos + assert_never(platform) + + +def build_in_directory(args: CommandLineArguments) -> None: + platform: PlatformName = _compute_platform(args) options = compute_options(platform=platform, command_line_arguments=args, env=os.environ) package_dir = options.globals.package_dir @@ -242,8 +276,9 @@ def build_in_directory(args: CommandLineArguments) -> None: print(msg, file=sys.stderr) sys.exit(2) + platform_module = get_platform_module(platform) identifiers = get_build_identifiers( - platform=platform, + platform_module=platform_module, build_selector=options.globals.build_selector, architectures=options.globals.architectures, ) @@ -289,14 +324,7 @@ def build_in_directory(args: CommandLineArguments) -> None: with cibuildwheel.util.print_new_wheels( "\n{n} wheels produced in {m:.0f} minutes:", output_dir ): - if platform == "linux": - cibuildwheel.linux.build(options, tmp_path) - elif platform == "windows": - cibuildwheel.windows.build(options, tmp_path) - elif platform == "macos": - cibuildwheel.macos.build(options, tmp_path) - else: - assert_never(platform) + platform_module.build(options, tmp_path) finally: # avoid https://github.com/python/cpython/issues/86962 by performing # cleanup manually @@ -305,7 +333,7 @@ def build_in_directory(args: CommandLineArguments) -> None: log.warning(f"Can't delete temporary folder '{tmp_path}'") -def print_preamble(platform: str, options: Options, identifiers: list[str]) -> None: +def print_preamble(platform: str, options: Options, identifiers: Sequence[str]) -> None: print( textwrap.dedent( """ @@ -339,33 +367,13 @@ def print_preamble(platform: str, options: Options, identifiers: list[str]) -> N def get_build_identifiers( - platform: PlatformName, build_selector: BuildSelector, architectures: set[Architecture] + platform_module: PlatformModule, build_selector: BuildSelector, architectures: Set[Architecture] ) -> list[str]: - python_configurations: ( - list[cibuildwheel.linux.PythonConfiguration] - | list[cibuildwheel.windows.PythonConfiguration] - | list[cibuildwheel.macos.PythonConfiguration] - ) - - if platform == "linux": - python_configurations = cibuildwheel.linux.get_python_configurations( - build_selector, architectures - ) - elif platform == "windows": - python_configurations = cibuildwheel.windows.get_python_configurations( - build_selector, architectures - ) - elif platform == "macos": - python_configurations = cibuildwheel.macos.get_python_configurations( - build_selector, architectures - ) - else: - assert_never(platform) - + python_configurations = platform_module.get_python_configurations(build_selector, architectures) return [config.identifier for config in python_configurations] -def detect_warnings(*, options: Options, identifiers: list[str]) -> list[str]: +def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]: warnings = [] # warn about deprecated {python} and {pip} diff --git a/cibuildwheel/_compat/__init__.py b/cibuildwheel/_compat/__init__.py new file mode 100644 index 00000000..9d48db4f --- /dev/null +++ b/cibuildwheel/_compat/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/cibuildwheel/functools_cached_property_38.py b/cibuildwheel/_compat/_functools_cached_property_38.py similarity index 94% rename from cibuildwheel/functools_cached_property_38.py rename to cibuildwheel/_compat/_functools_cached_property_38.py index 879cdb62..134564b5 100644 --- a/cibuildwheel/functools_cached_property_38.py +++ b/cibuildwheel/_compat/_functools_cached_property_38.py @@ -1,7 +1,9 @@ from __future__ import annotations +import typing +from collections.abc import Callable from threading import RLock -from typing import Any, Callable, Generic, TypeVar, overload +from typing import Any, Generic, TypeVar __all__ = ["cached_property"] @@ -24,11 +26,11 @@ class cached_property(Generic[_T]): msg = f"Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r})." raise TypeError(msg) - @overload + @typing.overload def __get__(self, instance: None, owner: type[Any] | None = ...) -> cached_property[_T]: ... - @overload + @typing.overload def __get__(self, instance: object, owner: type[Any] | None = ...) -> _T: ... diff --git a/cibuildwheel/_compat/functools.py b/cibuildwheel/_compat/functools.py new file mode 100644 index 00000000..8fe86a3e --- /dev/null +++ b/cibuildwheel/_compat/functools.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import sys + +if sys.version_info >= (3, 8): + from functools import cached_property +else: + from ._functools_cached_property_38 import cached_property + +__all__ = ("cached_property",) diff --git a/cibuildwheel/_compat/tomllib.py b/cibuildwheel/_compat/tomllib.py new file mode 100644 index 00000000..f3b30fe6 --- /dev/null +++ b/cibuildwheel/_compat/tomllib.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import sys + +if sys.version_info >= (3, 11): + from tomllib import load # noqa: TID251 +else: + from tomli import load # noqa: TID251 + +__all__ = ("load",) diff --git a/cibuildwheel/_compat/typing.py b/cibuildwheel/_compat/typing.py new file mode 100644 index 00000000..62495adf --- /dev/null +++ b/cibuildwheel/_compat/typing.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import sys + +if sys.version_info < (3, 8): + from typing_extensions import Final, Literal, OrderedDict, Protocol, TypedDict +else: + from typing import Final, Literal, OrderedDict, Protocol, TypedDict # noqa: TID251 + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, assert_never +else: + from typing import NotRequired, assert_never # noqa: TID251 + +__all__ = ( + "Final", + "Literal", + "Protocol", + "Protocol", + "TypedDict", + "OrderedDict", + "assert_never", + "NotRequired", +) diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index e1c8482a..a891ca2a 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -4,11 +4,17 @@ import functools import platform as platform_module import re import sys +from collections.abc import Set from enum import Enum -from .typing import Final, Literal, PlatformName, assert_never +from ._compat.typing import Final, Literal, assert_never +from .typing import PlatformName -PRETTY_NAMES: Final = {"linux": "Linux", "macos": "macOS", "windows": "Windows"} +PRETTY_NAMES: Final[dict[PlatformName, str]] = { + "linux": "Linux", + "macos": "macOS", + "windows": "Windows", +} ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [ {"linux": "x86_64", "macos": "x86_64", "windows": "AMD64"}, @@ -132,7 +138,7 @@ class Architecture(Enum): def allowed_architectures_check( platform: PlatformName, - architectures: set[Architecture], + architectures: Set[Architecture], ) -> None: allowed_architectures = Architecture.all_archs(platform) diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py index 01093a62..c5f80537 100644 --- a/cibuildwheel/bashlex_eval.py +++ b/cibuildwheel/bashlex_eval.py @@ -1,8 +1,9 @@ from __future__ import annotations import subprocess +from collections.abc import Iterable, Mapping, Sequence from dataclasses import dataclass -from typing import Callable, Dict, List, Sequence +from typing import Callable, Dict, List # noqa: TID251 import bashlex @@ -10,7 +11,7 @@ import bashlex EnvironmentExecutor = Callable[[List[str], Dict[str, str]], str] -def local_environment_executor(command: list[str], env: dict[str, str]) -> str: +def local_environment_executor(command: Sequence[str], env: Mapping[str, str]) -> str: return subprocess.run(command, env=env, text=True, stdout=subprocess.PIPE, check=True).stdout @@ -22,7 +23,7 @@ class NodeExecutionContext: def evaluate( - value: str, environment: dict[str, str], executor: EnvironmentExecutor | None = None + value: str, environment: Mapping[str, str], executor: EnvironmentExecutor | None = None ) -> str: if not value: # empty string evaluates to empty string @@ -40,7 +41,9 @@ def evaluate( return evaluate_node( value_word_node, context=NodeExecutionContext( - environment=environment, input=value, executor=executor or local_environment_executor + environment=dict(environment), + input=value, + executor=executor or local_environment_executor, ), ) @@ -105,7 +108,7 @@ def evaluate_nodes_as_compound_command( def evaluate_nodes_as_simple_command( - nodes: list[bashlex.ast.node], context: NodeExecutionContext + nodes: Iterable[bashlex.ast.node], context: NodeExecutionContext ) -> str: command = [evaluate_node(part, context=context) for part in nodes] return context.executor(command, context.environment) diff --git a/cibuildwheel/environment.py b/cibuildwheel/environment.py index fd17175d..3117b54e 100644 --- a/cibuildwheel/environment.py +++ b/cibuildwheel/environment.py @@ -1,14 +1,14 @@ from __future__ import annotations import dataclasses -from typing import Any, Mapping, Sequence +from collections.abc import Mapping, Sequence +from typing import Any import bashlex import bashlex.errors -from cibuildwheel.typing import Protocol - from . import bashlex_eval +from ._compat.typing import Protocol class EnvironmentParseError(Exception): @@ -54,7 +54,7 @@ class EnvironmentAssignment(Protocol): def evaluated_value( self, *, - environment: dict[str, str], + environment: Mapping[str, str], executor: bashlex_eval.EnvironmentExecutor | None = None, ) -> str: """Returns the value of this assignment, as evaluated in the environment""" @@ -91,7 +91,7 @@ class EnvironmentAssignmentBash: def evaluated_value( self, - environment: dict[str, str], + environment: Mapping[str, str], executor: bashlex_eval.EnvironmentExecutor | None = None, ) -> str: return bashlex_eval.evaluate(self.value, environment=environment, executor=executor) @@ -117,7 +117,7 @@ class ParsedEnvironment: prev_environment: Mapping[str, str], executor: bashlex_eval.EnvironmentExecutor | None = None, ) -> dict[str, str]: - environment = dict(**prev_environment) + environment = {**prev_environment} for assignment in self.assignments: value = assignment.evaluated_value(environment=environment, executor=executor) diff --git a/cibuildwheel/extra.py b/cibuildwheel/extra.py index 36389e6f..5ac9ea1f 100644 --- a/cibuildwheel/extra.py +++ b/cibuildwheel/extra.py @@ -4,9 +4,10 @@ These are utilities for the `/bin` scripts, not for the `cibuildwheel` program. from __future__ import annotations +from collections.abc import Mapping, Sequence from io import StringIO -from .typing import Protocol +from ._compat.typing import Protocol __all__ = ("Printable", "dump_python_configurations") @@ -16,7 +17,9 @@ class Printable(Protocol): ... -def dump_python_configurations(inp: dict[str, dict[str, list[dict[str, Printable]]]]) -> str: +def dump_python_configurations( + inp: Mapping[str, Mapping[str, Sequence[Mapping[str, Printable]]]] +) -> str: output = StringIO() for header, values in inp.items(): output.write(f"[{header}]\n") diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index acf00667..44bafc55 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -3,19 +3,22 @@ from __future__ import annotations import subprocess import sys import textwrap +from collections.abc import Iterable, Iterator, Sequence, Set from dataclasses import dataclass from pathlib import Path, PurePath, PurePosixPath -from typing import Iterator, Tuple +from typing import Tuple +from ._compat.typing import OrderedDict, assert_never from .architecture import Architecture from .logger import log from .oci_container import OCIContainer from .options import Options -from .typing import OrderedDict, PathOrStr, assert_never +from .typing import PathOrStr from .util import ( AlreadyBuiltWheelError, BuildSelector, NonPlatformWheelError, + build_frontend_or_default, find_compatible_wheel, get_build_verbosity_extra_flags, prepare_command, @@ -46,7 +49,7 @@ class BuildStep: def get_python_configurations( build_selector: BuildSelector, - architectures: set[Architecture], + architectures: Set[Architecture], ) -> list[PythonConfiguration]: full_python_configs = read_python_configs("linux") @@ -111,7 +114,7 @@ def get_build_steps( def check_all_python_exist( - *, platform_configs: list[PythonConfiguration], container: OCIContainer + *, platform_configs: Iterable[PythonConfiguration], container: OCIContainer ) -> None: exist = True messages = [] @@ -136,7 +139,7 @@ def check_all_python_exist( def build_in_container( *, options: Options, - platform_configs: list[PythonConfiguration], + platform_configs: Sequence[PythonConfiguration], container: OCIContainer, container_project_path: PurePath, container_package_dir: PurePath, @@ -174,6 +177,7 @@ def build_in_container( for config in platform_configs: log.build_start(config.identifier) build_options = options.build_options(config.identifier) + build_frontend = build_frontend_or_default(build_options.build_frontend) dependency_constraint_flags: list[PathOrStr] = [] @@ -239,11 +243,10 @@ def build_in_container( container.call(["rm", "-rf", built_wheel_dir]) container.call(["mkdir", "-p", built_wheel_dir]) - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - extra_flags = split_config_settings(build_options.config_settings) + extra_flags = split_config_settings(build_options.config_settings, build_frontend) - if build_options.build_frontend == "pip": - extra_flags += verbosity_flags + if build_frontend == "pip": + extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) container.call( [ "python", @@ -257,9 +260,10 @@ def build_in_container( ], env=env, ) - elif build_options.build_frontend == "build": - verbosity_setting = " ".join(verbosity_flags) - extra_flags += (f"--config-setting={verbosity_setting}",) + elif build_frontend == "build": + 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) container.call( [ "python", @@ -273,7 +277,7 @@ def build_in_container( env=env, ) else: - assert_never(build_options.build_frontend) + assert_never(build_frontend) built_wheel = container.glob(built_wheel_dir, "*.whl")[0] @@ -375,7 +379,9 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 try: # check the container engine is installed subprocess.run( - [options.globals.container_engine, "--version"], check=True, stdout=subprocess.DEVNULL + [options.globals.container_engine.name, "--version"], + check=True, + stdout=subprocess.DEVNULL, ) except subprocess.CalledProcessError: print( @@ -435,7 +441,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 sys.exit(1) -def _matches_prepared_command(error_cmd: list[str], command_template: str) -> bool: +def _matches_prepared_command(error_cmd: Sequence[str], command_template: str) -> bool: if len(error_cmd) < 3 or error_cmd[0:2] != ["sh", "-c"]: return False command_prefix = command_template.split("{", maxsplit=1)[0].strip() diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 014f3b00..9a546c97 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -5,19 +5,20 @@ import os import re import sys import time -from typing import IO, AnyStr +from typing import IO, AnyStr, Tuple -from cibuildwheel.typing import Final -from cibuildwheel.util import CIProvider, detect_ci_provider +from ._compat.typing import Final +from .util import CIProvider, detect_ci_provider -DEFAULT_FOLD_PATTERN: Final = ("{name}", "") -FOLD_PATTERNS: Final = { +FoldPattern = Tuple[str, str] +DEFAULT_FOLD_PATTERN: Final[FoldPattern] = ("{name}", "") +FOLD_PATTERNS: Final[dict[str, FoldPattern]] = { "azure": ("##[group]{name}", "##[endgroup]"), "travis": ("travis_fold:start:{identifier}\n{name}", "travis_fold:end:{identifier}"), "github": ("::group::{name}", "::endgroup::{name}"), } -PLATFORM_IDENTIFIER_DESCRIPTIONS: Final = { +PLATFORM_IDENTIFIER_DESCRIPTIONS: Final[dict[str, str]] = { "manylinux_x86_64": "manylinux x86_64", "manylinux_i686": "manylinux i686", "manylinux_aarch64": "manylinux aarch64", diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index a94caa66..1c103629 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -8,23 +8,27 @@ import re import shutil import subprocess import sys +import typing +from collections.abc import Sequence, Set from dataclasses import dataclass from pathlib import Path -from typing import Sequence, Tuple, cast +from typing import Tuple from filelock import FileLock +from ._compat.typing import Literal, assert_never from .architecture import Architecture from .environment import ParsedEnvironment from .logger import log from .options import Options -from .typing import Literal, PathOrStr, assert_never +from .typing import PathOrStr from .util import ( CIBW_CACHE_PATH, AlreadyBuiltWheelError, BuildFrontend, BuildSelector, NonPlatformWheelError, + build_frontend_or_default, call, detect_ci_provider, download, @@ -53,7 +57,7 @@ def get_macos_version() -> tuple[int, int]: """ version_str, _, _ = platform.mac_ver() version = tuple(map(int, version_str.split(".")[:2])) - return cast(Tuple[int, int], version) + return typing.cast(Tuple[int, int], version) def get_macos_sdks() -> list[str]: @@ -69,7 +73,7 @@ class PythonConfiguration: def get_python_configurations( - build_selector: BuildSelector, architectures: set[Architecture] + build_selector: BuildSelector, architectures: Set[Architecture] ) -> list[PythonConfiguration]: full_python_configs = read_python_configs("macos") @@ -330,6 +334,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) + build_frontend = build_frontend_or_default(build_options.build_frontend) log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier @@ -352,7 +357,7 @@ def build(options: Options, tmp_path: Path) -> None: config, dependency_constraint_flags, build_options.environment, - build_options.build_frontend, + build_frontend, ) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) @@ -373,11 +378,10 @@ def build(options: Options, tmp_path: Path) -> None: log.step("Building wheel...") built_wheel_dir.mkdir() - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - extra_flags = split_config_settings(build_options.config_settings) + extra_flags = split_config_settings(build_options.config_settings, build_frontend) - if build_options.build_frontend == "pip": - extra_flags += verbosity_flags + if build_frontend == "pip": + extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # see https://github.com/pypa/cibuildwheel/pull/369 call( @@ -391,9 +395,10 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=env, ) - elif build_options.build_frontend == "build": - verbosity_setting = " ".join(verbosity_flags) - extra_flags += (f"--config-setting={verbosity_setting}",) + elif build_frontend == "build": + 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: constraint_path = ( @@ -414,7 +419,7 @@ def build(options: Options, tmp_path: Path) -> None: env=build_env, ) else: - assert_never(build_options.build_frontend) + assert_never(build_frontend) built_wheel = next(built_wheel_dir.glob("*.whl")) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index b92acceb..e0700126 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -8,16 +8,47 @@ import shlex import shutil import subprocess import sys +import typing import uuid +from collections.abc import Mapping, Sequence +from dataclasses import dataclass from pathlib import Path, PurePath, PurePosixPath from types import TracebackType -from typing import IO, Dict, Sequence, cast +from typing import IO, Dict -from cibuildwheel.util import CIProvider, detect_ci_provider +from ._compat.typing import Literal +from .typing import PathOrStr, PopenBytes +from .util import CIProvider, detect_ci_provider, parse_key_value_string -from .typing import Literal, PathOrStr, PopenBytes +ContainerEngineName = Literal["docker", "podman"] -ContainerEngine = Literal["docker", "podman"] + +@dataclass(frozen=True) +class OCIContainerEngineConfig: + name: ContainerEngineName + create_args: Sequence[str] = () + + @staticmethod + def from_config_string(config_string: str) -> OCIContainerEngineConfig: + config_dict = parse_key_value_string(config_string, ["name"]) + name = " ".join(config_dict["name"]) + if name not in {"docker", "podman"}: + msg = f"unknown container engine {name}" + raise ValueError(msg) + + name = typing.cast(ContainerEngineName, name) + # some flexibility in the option name to cope with TOML conventions + create_args = config_dict.get("create_args") or config_dict.get("create-args") or [] + return OCIContainerEngineConfig(name=name, create_args=create_args) + + def options_summary(self) -> str | dict[str, str]: + if not self.create_args: + return self.name + else: + return {"name": self.name, "create_args": repr(self.create_args)} + + +DEFAULT_ENGINE = OCIContainerEngineConfig("docker") class OCIContainer: @@ -55,7 +86,7 @@ class OCIContainer: image: str, simulate_32_bit: bool = False, cwd: PathOrStr | None = None, - engine: ContainerEngine = "docker", + engine: OCIContainerEngineConfig = DEFAULT_ENGINE, ): if not image: msg = "Must have a non-empty image to run." @@ -82,13 +113,14 @@ class OCIContainer: subprocess.run( [ - self.engine, + self.engine.name, "create", "--env=CIBUILDWHEEL", f"--name={self.name}", "--interactive", "--volume=/:/host", # ignored on CircleCI *network_args, + *self.engine.create_args, self.image, *shell_args, ], @@ -97,7 +129,7 @@ class OCIContainer: self.process = subprocess.Popen( [ - self.engine, + self.engine.name, "start", "--attach", "--interactive", @@ -135,7 +167,7 @@ class OCIContainer: self.bash_stdin.close() self.bash_stdout.close() - if self.engine == "podman": + if self.engine.name == "podman": # This works around what seems to be a race condition in the podman # backend. The full reason is not understood. See PR #966 for a # discussion on possible causes and attempts to remove this line. @@ -145,7 +177,7 @@ class OCIContainer: assert isinstance(self.name, str) subprocess.run( - [self.engine, "rm", "--force", "-v", self.name], + [self.engine.name, "rm", "--force", "-v", self.name], stdout=subprocess.DEVNULL, check=False, ) @@ -160,15 +192,16 @@ class OCIContainer: if from_path.is_dir(): self.call(["mkdir", "-p", to_path]) subprocess.run( - f"tar cf - . | {self.engine} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -", + f"tar cf - . | {self.engine.name} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -", shell=True, check=True, cwd=from_path, ) else: + exec_process: subprocess.Popen[bytes] with subprocess.Popen( [ - self.engine, + self.engine.name, "exec", "-i", str(self.name), @@ -178,10 +211,10 @@ class OCIContainer: ], stdin=subprocess.PIPE, ) as exec_process: - exec_process.stdin = cast(IO[bytes], exec_process.stdin) - + assert exec_process.stdin with open(from_path, "rb") as from_file: - shutil.copyfileobj(from_file, exec_process.stdin) + # Bug in mypy, https://github.com/python/mypy/issues/15031 + shutil.copyfileobj(from_file, exec_process.stdin) # type: ignore[misc] exec_process.stdin.close() exec_process.wait() @@ -195,10 +228,10 @@ class OCIContainer: # note: we assume from_path is a dir to_path.mkdir(parents=True, exist_ok=True) - if self.engine == "podman": + if self.engine.name == "podman": subprocess.run( [ - self.engine, + self.engine.name, "cp", f"{self.name}:{from_path}/.", str(to_path), @@ -206,10 +239,10 @@ class OCIContainer: check=True, cwd=to_path, ) - elif self.engine == "docker": + 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} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -" + command = f"{self.engine.name} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -" subprocess.run( command, shell=True, @@ -217,7 +250,7 @@ class OCIContainer: cwd=to_path, ) else: - raise KeyError(self.engine) + raise KeyError(self.engine.name) def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]: glob_pattern = path.joinpath(pattern) @@ -238,7 +271,7 @@ class OCIContainer: def call( self, args: Sequence[PathOrStr], - env: dict[str, str] | None = None, + env: Mapping[str, str] | None = None, capture_output: bool = False, cwd: PathOrStr | None = None, ) -> str: @@ -328,17 +361,17 @@ class OCIContainer: capture_output=True, ) ) - return cast(Dict[str, str], env) + return typing.cast(Dict[str, str], env) - def environment_executor(self, command: list[str], environment: dict[str, str]) -> str: + def environment_executor(self, command: Sequence[str], environment: dict[str, str]) -> str: # used as an EnvironmentExecutor to evaluate commands and capture output return self.call(command, env=environment, capture_output=True) def debug_info(self) -> str: - if self.engine == "podman": - command = f"{self.engine} info --debug" + if self.engine.name == "podman": + command = f"{self.engine.name} info --debug" else: - command = f"{self.engine} info" + command = f"{self.engine.name} info" completed = subprocess.run( command, shell=True, diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 6b7b335f..f9d96d0f 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -10,22 +10,20 @@ import shlex import sys import textwrap import traceback +from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Set from pathlib import Path -from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Union, cast - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib +from typing import Any, Dict, List, Union from packaging.specifiers import SpecifierSet +from ._compat import tomllib +from ._compat.typing import Literal, NotRequired, TypedDict from .architecture import Architecture from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .logger import log -from .oci_container import ContainerEngine +from .oci_container import OCIContainerEngineConfig from .projectfiles import get_requires_python_str -from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict +from .typing import PLATFORMS, PlatformName from .util import ( MANYLINUX_ARCHS, MUSLLINUX_ARCHS, @@ -76,7 +74,7 @@ class GlobalOptions: build_selector: BuildSelector test_selector: TestSelector architectures: set[Architecture] - container_engine: ContainerEngine + container_engine: OCIContainerEngineConfig @dataclasses.dataclass(frozen=True) @@ -94,7 +92,7 @@ class BuildOptions: test_requires: list[str] test_extras: str build_verbosity: int - build_frontend: BuildFrontend + build_frontend: BuildFrontend | Literal["default"] config_settings: str @property @@ -137,8 +135,14 @@ 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]] @@ -161,7 +165,7 @@ def _dig_first(*pairs: tuple[Mapping[str, Setting], str], ignore_empty: bool = F if key in dict_like: value = dict_like[key] - if ignore_empty and value == "": + if ignore_empty and value == "": # noqa: PLC1901 continue return value @@ -193,7 +197,7 @@ class OptionsReader: *, platform: PlatformName, env: Mapping[str, str], - disallow: dict[str, set[str]] | None = None, + disallow: Mapping[str, Set[str]] | None = None, ) -> None: self.platform = platform self.env = env @@ -455,15 +459,17 @@ class Options: ) test_selector = TestSelector(skip_config=test_skip) - container_engine_str = self.reader.get("container-engine") + container_engine_str = self.reader.get( + "container-engine", table={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote} + ) - if container_engine_str not in ["docker", "podman"]: - msg = f"cibuildwheel: Unrecognised container_engine {container_engine_str!r}, only 'docker' and 'podman' are supported" + 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) - container_engine = cast(ContainerEngine, container_engine_str) - return GlobalOptions( package_dir=package_dir, output_dir=output_dir, @@ -499,11 +505,13 @@ class Options: test_extras = self.reader.get("test-extras", sep=",") build_verbosity_str = self.reader.get("build-verbosity") - build_frontend: BuildFrontend + build_frontend: BuildFrontend | Literal["default"] if build_frontend_str == "build": build_frontend = "build" elif build_frontend_str == "pip": build_frontend = "pip" + elif build_frontend_str == "default": + build_frontend = "default" else: msg = f"cibuildwheel: Unrecognised build frontend {build_frontend_str!r}, only 'pip' and 'build' are supported" print(msg, file=sys.stderr) @@ -597,7 +605,7 @@ class Options: config_settings=config_settings, ) - def check_for_invalid_configuration(self, identifiers: list[str]) -> None: + def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None: if self.platform in {"macos", "windows"}: before_all_values = {self.build_options(i).before_all for i in identifiers} @@ -629,7 +637,7 @@ class Options: read_config_file=False, ) - def summary(self, identifiers: list[str]) -> str: + def summary(self, identifiers: Iterable[str]) -> str: lines = [] global_option_names = sorted(f.name for f in dataclasses.fields(self.globals)) @@ -667,7 +675,7 @@ class Options: option_name: str, option_value: Any, default_value: Any, - overrides: dict[str, Any] | None = None, + overrides: Mapping[str, Any] | None = None, ) -> str: """ Return a summary of the option value, including any overrides, with diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index 0b87415b..95dc150d 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -7,10 +7,7 @@ import sys from pathlib import Path from typing import Any -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib +from ._compat import tomllib if sys.version_info < (3, 8): Constant = ast.Str diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml index 601d8d46..1b605eaf 100644 --- a/cibuildwheel/resources/build-platforms.toml +++ b/cibuildwheel/resources/build-platforms.toml @@ -6,12 +6,14 @@ python_configurations = [ { identifier = "cp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-manylinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-manylinux_i686", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-manylinux_i686", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-manylinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-manylinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-manylinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-manylinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-manylinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "pp37-manylinux_x86_64", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" }, { identifier = "pp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" }, { identifier = "pp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" }, @@ -21,18 +23,21 @@ python_configurations = [ { identifier = "cp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-manylinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-manylinux_ppc64le", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-manylinux_ppc64le", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-manylinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-manylinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-manylinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-manylinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-manylinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-manylinux_s390x", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-manylinux_s390x", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-manylinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-manylinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-manylinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-manylinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-manylinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "pp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" }, { identifier = "pp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" }, { identifier = "pp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" }, @@ -45,30 +50,35 @@ python_configurations = [ { identifier = "cp39-musllinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-musllinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-musllinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-musllinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-musllinux_i686", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-musllinux_i686", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-musllinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-musllinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-musllinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-musllinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-musllinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-musllinux_aarch64", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-musllinux_aarch64", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-musllinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-musllinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-musllinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-musllinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-musllinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-musllinux_ppc64le", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-musllinux_ppc64le", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-musllinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-musllinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-musllinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-musllinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-musllinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp36-musllinux_s390x", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, { identifier = "cp37-musllinux_s390x", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, { identifier = "cp38-musllinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp39-musllinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp310-musllinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp311-musllinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" }, + { identifier = "cp312-musllinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" }, ] [macos] @@ -81,12 +91,15 @@ python_configurations = [ { identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" }, { identifier = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" }, { identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" }, - { identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.9/python-3.10.9-macos11.pkg" }, - { identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.9/python-3.10.9-macos11.pkg" }, - { identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.9/python-3.10.9-macos11.pkg" }, - { identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.1/python-3.11.1-macos11.pkg" }, - { identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.1/python-3.11.1-macos11.pkg" }, - { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.1/python-3.11.1-macos11.pkg" }, + { identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" }, + { identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" }, + { identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" }, + { identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.4/python-3.11.4-macos11.pkg" }, + { identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.4/python-3.11.4-macos11.pkg" }, + { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.4/python-3.11.4-macos11.pkg" }, + { identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.0/python-3.12.0b2-macos11.pkg" }, + { identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.0/python-3.12.0b2-macos11.pkg" }, + { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.0/python-3.12.0b2-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" }, @@ -104,13 +117,16 @@ python_configurations = [ { identifier = "cp38-win_amd64", version = "3.8.10", arch = "64" }, { identifier = "cp39-win32", version = "3.9.13", arch = "32" }, { identifier = "cp39-win_amd64", version = "3.9.13", arch = "64" }, - { identifier = "cp310-win32", version = "3.10.9", arch = "32" }, - { identifier = "cp310-win_amd64", version = "3.10.9", arch = "64" }, - { identifier = "cp311-win32", version = "3.11.1", arch = "32" }, - { identifier = "cp311-win_amd64", version = "3.11.1", arch = "64" }, + { identifier = "cp310-win32", version = "3.10.11", arch = "32" }, + { identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" }, + { identifier = "cp311-win32", version = "3.11.4", arch = "32" }, + { identifier = "cp311-win_amd64", version = "3.11.4", arch = "64" }, + { identifier = "cp312-win32", version = "3.12.0-b2", arch = "32" }, + { identifier = "cp312-win_amd64", version = "3.12.0-b2", arch = "64" }, { identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" }, - { identifier = "cp310-win_arm64", version = "3.10.8", arch = "ARM64" }, - { identifier = "cp311-win_arm64", version = "3.11.1", arch = "ARM64" }, + { identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" }, + { identifier = "cp311-win_arm64", version = "3.11.4", arch = "ARM64" }, + { identifier = "cp312-win_arm64", version = "3.12.0-b2", 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.11-win64.zip" }, diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index c6f6cbe3..3d88a90b 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -8,21 +8,21 @@ delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via delocate -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index c2fa590f..b05133bf 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -8,21 +8,21 @@ delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via delocate -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python312.txt b/cibuildwheel/resources/constraints-python312.txt new file mode 100644 index 00000000..6e8f761c --- /dev/null +++ b/cibuildwheel/resources/constraints-python312.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# nox -s update_constraints-3.12 +# +delocate==0.10.4 + # via -r cibuildwheel/resources/constraints.in +distlib==0.3.6 + # via virtualenv +filelock==3.12.1 + # via virtualenv +packaging==23.1 + # via delocate +platformdirs==3.5.3 + # via virtualenv +typing-extensions==4.6.3 + # via delocate +virtualenv==20.23.0 + # via -r cibuildwheel/resources/constraints.in +wheel==0.40.0 + # via -r cibuildwheel/resources/constraints.in + +# The following packages are considered to be unsafe in a requirements file: +pip==23.1.2 + # via -r cibuildwheel/resources/constraints.in +setuptools==67.8.0 + # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python37.txt b/cibuildwheel/resources/constraints-python37.txt index 2ff0390f..5f80dc5e 100644 --- a/cibuildwheel/resources/constraints-python37.txt +++ b/cibuildwheel/resources/constraints-python37.txt @@ -8,28 +8,28 @@ delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -importlib-metadata==6.0.0 +importlib-metadata==6.6.0 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via # delocate # importlib-metadata # platformdirs -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in -zipp==3.12.1 +zipp==3.15.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 2cd59960..beeb3aa6 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -8,21 +8,21 @@ delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via delocate -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index e853692d..a3655063 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -8,21 +8,21 @@ delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via delocate -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index c2fa590f..6e8f761c 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -1,28 +1,28 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# nox -s update_constraints-3.11 +# nox -s update_constraints-3.12 # delocate==0.10.4 # via -r cibuildwheel/resources/constraints.in distlib==0.3.6 # via virtualenv -filelock==3.9.0 +filelock==3.12.1 # via virtualenv -packaging==23.0 +packaging==23.1 # via delocate -platformdirs==2.6.2 +platformdirs==3.5.3 # via virtualenv -typing-extensions==4.4.0 +typing-extensions==4.6.3 # via delocate -virtualenv==20.17.1 +virtualenv==20.23.0 # via -r cibuildwheel/resources/constraints.in -wheel==0.38.4 +wheel==0.40.0 # via -r cibuildwheel/resources/constraints.in # The following packages are considered to be unsafe in a requirements file: -pip==23.0 +pip==23.1.2 # via -r cibuildwheel/resources/constraints.in -setuptools==67.1.0 +setuptools==67.8.0 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index d16a3147..40b42f6e 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -4,7 +4,7 @@ skip = "" test-skip = "" archs = ["auto"] -build-frontend = "pip" +build-frontend = "default" config-settings = {} dependency-versions = "pinned" environment = {} diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg index 85b4b4dd..65635fed 100644 --- a/cibuildwheel/resources/pinned_docker_images.cfg +++ b/cibuildwheel/resources/pinned_docker_images.cfg @@ -1,49 +1,49 @@ [x86_64] -manylinux1 = quay.io/pypa/manylinux1_x86_64:2023-02-05-7c3bb1c +manylinux1 = quay.io/pypa/manylinux1_x86_64:2023-06-07-3623bb5 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2023-06-11-02cacaf 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:2023-02-05-56647d4 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2023-06-11-02cacaf +musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2023-06-11-02cacaf [i686] -manylinux1 = quay.io/pypa/manylinux1_i686:2023-02-05-7c3bb1c +manylinux1 = quay.io/pypa/manylinux1_i686:2023-06-07-3623bb5 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2023-02-05-56647d4 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2023-06-11-02cacaf [pypy_x86_64] manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2023-06-11-02cacaf 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:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2023-06-11-02cacaf [pypy_i686] manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463 [aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2023-02-05-56647d4 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2023-06-11-02cacaf +musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2023-06-11-02cacaf [ppc64le] -manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-12-26-0d38463 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2023-01-03-129be5e -musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2023-06-11-02cacaf +musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2023-06-11-02cacaf [s390x] -manylinux2014 = quay.io/pypa/manylinux2014_s390x:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_s390x:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-12-26-0d38463 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2023-02-05-56647d4 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2023-06-11-02cacaf +musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2023-06-11-02cacaf [pypy_aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2023-02-05-56647d4 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2023-06-11-02cacaf manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2023-02-05-56647d4 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2023-06-11-02cacaf diff --git a/cibuildwheel/resources/virtualenv.toml b/cibuildwheel/resources/virtualenv.toml index feb0327e..446d0540 100644 --- a/cibuildwheel/resources/virtualenv.toml +++ b/cibuildwheel/resources/virtualenv.toml @@ -1,2 +1,2 @@ -version = "20.17.1" -url = "https://github.com/pypa/get-virtualenv/blob/20.17.1/public/virtualenv.pyz?raw=true" +version = "20.21.1" +url = "https://github.com/pypa/get-virtualenv/blob/20.21.1/public/virtualenv.pyz?raw=true" diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index 6c8fe9fc..7abd9e3c 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -2,37 +2,21 @@ from __future__ import annotations import os import subprocess -import sys -from typing import TYPE_CHECKING, Union +import typing +from typing import Union -if sys.version_info < (3, 8): - from typing_extensions import Final, Literal, OrderedDict, Protocol, TypedDict -else: - from typing import Final, Literal, OrderedDict, Protocol, TypedDict - -if sys.version_info < (3, 11): - from typing_extensions import NotRequired, assert_never -else: - from typing import NotRequired, assert_never +from ._compat.typing import Final, Literal, Protocol __all__ = ( - "Final", - "Literal", "PLATFORMS", "PathOrStr", "PlatformName", - "Protocol", "PLATFORMS", "PopenBytes", - "Protocol", - "TypedDict", - "OrderedDict", - "assert_never", - "NotRequired", ) -if TYPE_CHECKING: +if typing.TYPE_CHECKING: PopenBytes = subprocess.Popen[bytes] PathOrStr = Union[str, os.PathLike[str]] else: @@ -42,3 +26,9 @@ else: PlatformName = Literal["linux", "macos", "windows"] PLATFORMS: Final[set[PlatformName]] = {"linux", "macos", "windows"} + + +class GenericPythonConfiguration(Protocol): + @property + def identifier(self) -> str: + ... diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index fe9e3487..dfc6e65c 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -11,32 +11,19 @@ import subprocess import sys import textwrap import time +import typing import urllib.request +from collections import defaultdict +from collections.abc import Generator, Iterable, Mapping, Sequence from dataclasses import dataclass from enum import Enum from functools import lru_cache from pathlib import Path, PurePath from time import sleep -from typing import ( - Any, - ClassVar, - Generator, - Iterable, - Sequence, - TextIO, - TypeVar, - cast, - overload, -) +from typing import Any, ClassVar, TextIO, TypeVar import bracex import certifi - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib - from filelock import FileLock from packaging.requirements import InvalidRequirement, Requirement from packaging.specifiers import SpecifierSet @@ -44,7 +31,10 @@ from packaging.utils import parse_wheel_filename from packaging.version import Version from platformdirs import user_cache_path -from cibuildwheel.typing import Final, Literal, PathOrStr, PlatformName +from ._compat import tomllib +from ._compat.functools import cached_property +from ._compat.typing import Final, Literal +from .typing import PathOrStr, PlatformName __all__ = [ "resources_dir", @@ -71,6 +61,15 @@ test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py" BuildFrontend = Literal["pip", "build"] + +def build_frontend_or_default( + setting: BuildFrontend | Literal["default"], default: BuildFrontend = "pip" +) -> BuildFrontend: + if setting == "default": + return default + return setting + + MANYLINUX_ARCHS: Final[tuple[str, ...]] = ( "x86_64", "i686", @@ -98,20 +97,20 @@ CIBW_CACHE_PATH: Final[Path] = Path( IS_WIN: Final[bool] = sys.platform.startswith("win") -@overload +@typing.overload def call( *args: PathOrStr, - env: dict[str, str] | None = None, + env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None, capture_stdout: Literal[False] = ..., ) -> None: ... -@overload +@typing.overload def call( *args: PathOrStr, - env: dict[str, str] | None = None, + env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None, capture_stdout: Literal[True], ) -> str: @@ -120,7 +119,7 @@ def call( def call( *args: PathOrStr, - env: dict[str, str] | None = None, + env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None, capture_stdout: bool = False, ) -> str | None: @@ -140,10 +139,12 @@ def call( result = subprocess.run(args_, check=True, shell=IS_WIN, env=env, cwd=cwd, **kwargs) if not capture_stdout: return None - return cast(str, result.stdout) + return typing.cast(str, result.stdout) -def shell(*commands: str, env: dict[str, str] | None = None, cwd: PathOrStr | None = None) -> None: +def shell( + *commands: str, env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None +) -> None: command = " ".join(commands) print(f"+ {command}") subprocess.run(command, env=env, cwd=cwd, shell=True, check=True) @@ -208,9 +209,10 @@ def get_build_verbosity_extra_flags(level: int) -> list[str]: return [] -def split_config_settings(config_settings: str) -> list[str]: +def split_config_settings(config_settings: str, frontend: Literal["pip", "build"]) -> list[str]: config_settings_list = shlex.split(config_settings) - return [f"--config-setting={setting}" for setting in config_settings_list] + s = "s" if frontend == "pip" else "" + return [f"--config-setting{s}={setting}" for setting in config_settings_list] def read_python_configs(config: PlatformName) -> list[dict[str, str]]: @@ -250,7 +252,7 @@ class BuildSelector: requires_python: SpecifierSet | None = None # a pattern that skips prerelease versions, when include_prereleases is False. - PRERELEASE_SKIP: ClassVar[str] = "" + PRERELEASE_SKIP: ClassVar[str] = "cp312-*" prerelease_pythons: bool = False def __call__(self, build_id: str) -> bool: @@ -497,7 +499,7 @@ def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]: ) -def get_pip_version(env: dict[str, str]) -> str: +def get_pip_version(env: Mapping[str, str]) -> str: versions_output_text = call( "python", "-m", "pip", "freeze", "--all", capture_stdout=True, env=env ) @@ -543,8 +545,8 @@ def _parse_constraints_for_virtualenv( constraint_path = Path(dependency_constraint_flags[1]) assert constraint_path.exists() with constraint_path.open(encoding="utf-8") as constraint_file: - for line in constraint_file: - line = line.strip() + for line_ in constraint_file: + line = line_.strip() if not line: continue if line.startswith("#"): @@ -657,12 +659,6 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None: return None -if sys.version_info >= (3, 8): - from functools import cached_property -else: - from .functools_cached_property_38 import cached_property - - # Can be replaced by contextlib.chdir in Python 3.11 @contextlib.contextmanager def chdir(new_path: Path | str) -> Generator[None, None, None]: @@ -702,3 +698,40 @@ def fix_ansi_codes_for_github_actions(text: str) -> str: ansi_codes.append(code) return output + + +def parse_key_value_string( + key_value_string: str, positional_arg_names: list[str] | None = None +) -> dict[str, list[str]]: + """ + Parses a string like "docker; create_args: --some-option=value another-option" + """ + if positional_arg_names is None: + positional_arg_names = [] + + shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";:") + shlexer.commenters = "" + parts = list(shlexer) + # parts now looks like + # ['docker', ';', 'create_args',':', '--some-option=value', 'another-option'] + + # split by semicolon + fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k] + + result: dict[str, list[str]] = defaultdict(list) + for field_i, field in enumerate(fields): + if len(field) > 1 and field[1] == ":": + field_name = field[0] + values = field[2:] + else: + try: + field_name = positional_arg_names[field_i] + except IndexError: + msg = f"Failed to parse {key_value_string!r}. Too many positional arguments - expected a maximum of {len(positional_arg_names)}" + raise ValueError(msg) from None + + values = field + + result[field_name] += values + + return result diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 9182f046..d3e58786 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -6,26 +6,29 @@ 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 typing import Sequence from zipfile import ZipFile from filelock import FileLock from packaging.version import Version +from ._compat.typing import assert_never from .architecture import Architecture from .environment import ParsedEnvironment from .logger import log from .options import Options -from .typing import PathOrStr, assert_never +from .typing import PathOrStr from .util import ( CIBW_CACHE_PATH, AlreadyBuiltWheelError, BuildFrontend, BuildSelector, NonPlatformWheelError, + build_frontend_or_default, call, download, find_compatible_wheel, @@ -71,7 +74,7 @@ class PythonConfiguration: def get_python_configurations( build_selector: BuildSelector, - architectures: set[Architecture], + architectures: Set[Architecture], ) -> list[PythonConfiguration]: full_python_configs = read_python_configs("windows") @@ -135,7 +138,7 @@ def setup_setuptools_cross_compile( tmp: Path, python_configuration: PythonConfiguration, python_libs_base: Path, - env: dict[str, str], + env: MutableMapping[str, str], ) -> None: distutils_cfg = tmp / "extra-setup.cfg" env["DIST_EXTRA_CONFIG"] = str(distutils_cfg) @@ -183,7 +186,7 @@ def setup_rust_cross_compile( tmp: Path, # noqa: ARG001 python_configuration: PythonConfiguration, python_libs_base: Path, # noqa: ARG001 - env: dict[str, str], + env: MutableMapping[str, str], ) -> None: # Assume that MSVC will be used, because we already know that we are # cross-compiling. MinGW users can set CARGO_BUILD_TARGET themselves @@ -366,6 +369,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) + build_frontend = build_frontend_or_default(build_options.build_frontend) log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier @@ -386,7 +390,7 @@ def build(options: Options, tmp_path: Path) -> None: config, dependency_constraint_flags, build_options.environment, - build_options.build_frontend, + build_frontend, ) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) @@ -410,11 +414,10 @@ def build(options: Options, tmp_path: Path) -> None: log.step("Building wheel...") built_wheel_dir.mkdir() - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - extra_flags = split_config_settings(build_options.config_settings) + extra_flags = split_config_settings(build_options.config_settings, build_frontend) - if build_options.build_frontend == "pip": - extra_flags += verbosity_flags + if build_frontend == "pip": + extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # see https://github.com/pypa/cibuildwheel/pull/369 call( @@ -428,9 +431,10 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=env, ) - elif build_options.build_frontend == "build": - verbosity_setting = " ".join(verbosity_flags) - extra_flags += (f"--config-setting={verbosity_setting}",) + elif build_frontend == "build": + 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: constraints_path = ( @@ -461,7 +465,7 @@ def build(options: Options, tmp_path: Path) -> None: env=build_env, ) else: - assert_never(build_options.build_frontend) + assert_never(build_frontend) built_wheel = next(built_wheel_dir.glob("*.whl")) @@ -557,6 +561,9 @@ 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) diff --git a/docs/changelog.md b/docs/changelog.md index a4a64013..126b0f60 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,43 @@ title: Changelog # Changelog +### v2.13.1 + +_10 June 2023_ + +- 🛠 Updates the prerelease CPython 3.12 version to 3.12.0b2. (#1516) +- 🛠 Adds a moving `v.` tag for use in GitHub Actions workflow files. If you use this, you'll get the latest patch release within a minor version. Additionally, Dependabot won't send you PRs for patch releases. (#1517) + +### v2.13.0 + +_28 May 2023_ + +- ✨ Adds CPython 3.12 support, under the prerelease flag [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.readthedocs.io/en/stable/options/#prerelease-pythons). This version of cibuildwheel uses 3.12.0b1. + + While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.12 will be available in cibuildwheel without the flag. (#1507) + +- ✨ Adds the ability to pass arguments to the container engine when the container is created, using the [CIBW_CONTAINER_ENGINE](https://cibuildwheel.readthedocs.io/en/stable/options/#container-engine) option. (#1499) + +### v2.12.3 + +_19 April 2023_ + +- 🐛 Fix an import error when running on Python 3.7. (#1479) + +### v2.12.2 + +_18 April 2023_ + +- 🐛 Fix a bug that caused an extra empty config-setting to be passed to the backend when CIBW_BUILD_FRONTEND is set to `build`. (#1474) +- 🐛 Fix a crash that occurred when overwriting an existing wheel on Windows. (#1464) +- 🛠 Pinned version updates, including CPython 3.10.11, 3.11.3, pip 23.1 and wheel 0.40.0. + +### v2.12.1 + +_11 March 2023_ + +- 🐛 Fix a bug that prevented the use of CIBW_CONFIG_SETTINGS with the 'pip' build backend. (#1430) + ### v2.12.0 _16 Jan 2023_ diff --git a/docs/data/projects.yml b/docs/data/projects.yml index 82abe91a..cf993735 100644 --- a/docs/data/projects.yml +++ b/docs/data/projects.yml @@ -431,11 +431,6 @@ ci: [github] os: [apple, linux, windows] -- name: pydantic - gh: samuelcolvin/pydantic - ci: [github] - os: [apple, linux, windows] - - name: vaex gh: vaexio/vaex ci: [github] @@ -628,3 +623,10 @@ os: [windows, apple, linux] pypi: picologging notes: A high-performance logging library for Python. + +- name: aalink + gh: artfwo/aalink + ci: [github] + os: [windows, apple, linux] + pypi: aalink + notes: Async Python interface for Ableton Link. diff --git a/docs/faq.md b/docs/faq.md index f7256b89..f3497474 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -161,7 +161,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.12.0 +uses: pypa/cibuildwheel@v2.13.1 ``` 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. @@ -183,7 +183,7 @@ The second option, and the only one that supports other CI systems, is using a ` ```bash # requirements-cibw.txt -cibuildwheel==2.12.0 +cibuildwheel==2.13.1 ``` 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: @@ -327,7 +327,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.12.0 --output-dir wheelhouse +pipx run cibuildwheel==2.13.1 --output-dir wheelhouse pipx run twine upload wheelhouse/*.whl ``` diff --git a/docs/options.md b/docs/options.md index 257cd3bd..9d6da2b3 100644 --- a/docs/options.md +++ b/docs/options.md @@ -92,8 +92,8 @@ 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 natually a list. Multiline strings also work just -like in in the environment variables. Environment variables will take +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: @@ -226,6 +226,7 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat | Python 3.9 | cp39-macosx_x86_64
cp39-macosx_universal2
cp39-macosx_arm64 | cp39-win_amd64
cp39-win32
cp39-win_arm64 | cp39-manylinux_x86_64
cp39-manylinux_i686
cp39-musllinux_x86_64
cp39-musllinux_i686 | cp39-manylinux_aarch64
cp39-manylinux_ppc64le
cp39-manylinux_s390x
cp39-musllinux_aarch64
cp39-musllinux_ppc64le
cp39-musllinux_s390x | | Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x | | Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x | +| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x | | PyPy3.7 v7.3 | pp37-macosx_x86_64 | pp37-win_amd64 | pp37-manylinux_x86_64
pp37-manylinux_i686 | pp37-manylinux_aarch64 | | PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | | PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | @@ -237,7 +238,7 @@ For CPython, the minimally supported macOS version is 10.9; for PyPy 3.7, macOS Windows arm64 platform support is experimental. -See the [cibuildwheel 1 documentation](https://cibuildwheel.readthedocs.io/en/1.x/) for past end of life versions of Python, and PyPy2.7. +See the [cibuildwheel 1 documentation](https://cibuildwheel.readthedocs.io/en/1.x/) for past end-of-life versions of Python, and PyPy2.7. #### Examples @@ -314,7 +315,7 @@ See the [cibuildwheel 1 documentation](https://cibuildwheel.readthedocs.io/en/1. It is generally recommended to set `CIBW_BUILD` as an environment variable, though `skip` tends to be useful in a config file; you can statically declare that you don't - support pypy, for example. + support PyPy, for example.