diff --git a/.cirrus.yml b/.cirrus.yml index 7385d88a..e85ef479 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -6,6 +6,7 @@ run_tests: &RUN_TESTS linux_x86_task: + timeout_in: 120m compute_engine_instance: image_project: cirrus-images image: family/docker-builder @@ -33,7 +34,7 @@ linux_aarch64_task: windows_x86_task: # The task takes ~55 minutes while the timeout happens # after 60 minutes by default, let's allow some wiggle room. - timeout_in: 90m + timeout_in: 120m windows_container: image: cirrusci/windowsservercore:visualstudio2022 cpu: 8 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75f15052..deccf5dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.0 hooks: - id: pyupgrade args: ["--py37-plus"] diff --git a/README.md b/README.md index af5dbf5f..bfb40a63 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,10 @@ Usage |-----------------|-------|-------|---------|-----------|-----------| | GitHub Actions | ✅ | ✅ | ✅ | ✅¹ | ✅² | | Azure Pipelines | ✅ | ✅ | ✅ | | ✅² | -| Travis CI | ✅ | | ✅ | ✅ | ✅² | +| Travis CI | ✅ | | ✅ | ✅ | | | AppVeyor | ✅ | ✅ | ✅ | | ✅² | | CircleCI | ✅ | ✅ | | | ✅² | -| Gitlab CI | ✅ | | | | ✅² | +| Gitlab CI | ✅ | | | | | | Cirrus CI | ✅ | ✅³ | ✅ | ✅ | ✅ | ¹ [Requires emulation](https://cibuildwheel.readthedocs.io/en/stable/faq/#emulation), distributed separately. Other services may also support Linux ARM through emulation or third-party build hosts, but these are not tested in our CI.
@@ -92,7 +92,7 @@ jobs: - uses: actions/setup-python@v3 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.9.0 + run: python -m pip install cibuildwheel==2.10.2 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse @@ -208,6 +208,32 @@ Changelog +### v2.10.2 + +_25 September 2022_ + +- 🐛 Fix a bug that caused `win32` identifiers to fail when used with `--only`. (#1282) +- 🐛 Fix computation of `auto`/`auto64`/`auto32` archs when targeting a different platform to the one that you're running cibuildwheel on. (#1266) +- 📚 Fix an mistake in the 'how it works' diagram. (#1274) + +### v2.10.1 + +_18 September 2022_ + +- 🐛 Fix a bug that stopped environment variables specified in TOML from being expanded. (#1273) + +### v2.10.0 + +_13 September 2022_ + +- 🌟 Adds support for [building wheels on Cirrus CI](https://cibuildwheel.readthedocs.io/en/stable/setup/#cirrus-ci). This is exciting for us, as it's the first public CI platform that natively supports macOS Apple Silicon (aka. M1, `arm64`) runners. As such, it's the first platform that you can natively build _and test_ macOS `arm64` wheels. It also has native Linux ARM (aarch64) runners, for fast, native builds there. (#1191) +- 🌟 Adds support for running cibuildwheel on Apple Silicon machines. For a while, we've supported cross-compilation of Apple Silicon wheels on `x86_64`, but now that we have Cirrus CI we can run our test suite and officially support running cibuildwheel on `arm64`. (#1191) +- ✨ Adds the `--only` [command line option](https://cibuildwheel.readthedocs.io/en/stable/options/#command-line), to specify a single build to run. Previously, it could be cumbersome to set all the build selection options to target a specific build - for example, you might have to run something like `CIBW_BUILD=cp39-manylinux_x86_64 cibuildwheel --platform linux --archs x86_64`. The new `--only` option overrides all the build selection options to simplify running a single build, which now looks like `cibuildwheel --only cp39-manylinux_x86_64`. (#1098) +- ✨ Adds the [`CIBW_CONFIG_SETTINGS`](https://cibuildwheel.readthedocs.io/en/stable/options/#config-settings) option, so you can pass arguments to your package's build backend (#1244) +- 🛠 Updates the CPython 3.11 version to the latest release candidate - v3.11.0rc2. (#1265) +- 🐛 Fix a bug that can cause a RecursionError on Windows when building from an sdist. (#1253) +- 🛠 Add support for the s390x architecture on manylinux_2_28 (#1255) + ### v2.9.0 _11 August 2022_ @@ -226,34 +252,6 @@ _18 July 2022_ - 🛠 The GitHub Action will ensure a compatible version of Python is installed on the runner (#1114) - 📚 A few docs improvements -### v2.8.0 - -_5 July 2022_ - -- ✨ You can now run cibuildwheel on Podman, as an alternate container engine to Docker (which remains the default). This is useful in environments where a Docker daemon isn't available, for example, it can be run inside a Docker container, or without root access. To use Podman, set the [`CIBW_CONTAINER_ENGINE`](https://cibuildwheel.readthedocs.io/en/stable/options/#container-engine) option. (#966) -- ✨ Adds support for building `py3-none-{platform}` wheels. This works the same as ABI3 - wheels won't be rebuilt, but tests will still be run across all selected versions of Python. - - > These wheels contain native extension code, but don't use the Python APIs. Typically, they're bridged to Python using a FFI module like [ctypes](https://docs.python.org/3/library/ctypes.html) or [cffi](https://cffi.readthedocs.io/en/latest/). Because they don't use Python ABI, the wheels are more compatible - they work across many Python versions. - - Check out this [example ctypes project](https://github.com/joerick/python-ctypes-package-sample) to see an example of how it works. (#1151) -- 🛠 cibuildwheel will now error if multiple builds in a single run produce the same wheel filename, as this indicates a misconfiguration. (#1152) -- 📚 A few docs improvements and updates to keep things up-to-date. - -### v2.7.0 - -_17 June 2022_ - -- 🌟 Added support for the new `manylinux_2_28` images. These new images are based on AlmaLinux, the community-driven successor to CentOS, unlike manylinux_2_24, which was based on Debian. To build on these images, set your [`CIBW_MANYLINUX_*_IMAGE`](https://cibuildwheel.readthedocs.io/en/stable/options/#linux-image) option to `manylinux_2_28`. (#1026) -- 🐛 Fix a bug where tests were not being run on CPython 3.11 (when CIBW_PRERELEASE_PYTHONS was set) (#1138) -- ✨ You can now build Linux wheels on Windows, as long as you have Docker installed and set to 'Linux containers' (#1117) -- 🐛 Fix a bug on macOS that caused cibuildwheel to crash trying to overwrite a previously-built wheel of the same name. (#1129) - -### v2.6.1 - -_7 June 2022_ - -- 🛠 Update the prerelease CPython 3.11 to 3.11.0b3 - --- diff --git a/action.yml b/action.yml index a27269dd..111acc93 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,10 @@ inputs: description: 'File containing the config, defaults to {package}/pyproject.toml' required: false default: '' + only: + description: 'Build a specific wheel only. No need for arch/platform if this is set' + required: false + default: '' branding: icon: package color: yellow @@ -36,5 +40,6 @@ runs: ${{ inputs.package-dir }} --output-dir ${{ inputs.output-dir }} --config-file "${{ inputs.config-file }}" + --only "${{ inputs.only }}" 2>&1 shell: bash diff --git a/bin/inspect_all_known_projects.py b/bin/inspect_all_known_projects.py index 542e42b8..3f91b2b5 100755 --- a/bin/inspect_all_known_projects.py +++ b/bin/inspect_all_known_projects.py @@ -1,4 +1,17 @@ #!/usr/bin/env python3 + +""" +Check known projects for usage of requires-python. + +Usage: + + ./bin/inspect_all_known_projects.py --online=$GITHUB_TOKEN + +This will cache the results to all_known_setup.yaml; you can reprint +the results without the `--online` setting. +""" + + from __future__ import annotations import ast @@ -7,7 +20,7 @@ from typing import Iterator import click import yaml -from ghapi.core import GhApi, HTTP404NotFoundError +from github import Github, GithubException from rich import print from cibuildwheel.projectfiles import Analyzer @@ -47,33 +60,38 @@ def check_repo(name: str, contents: str) -> str: class MaybeRemote: - def __init__(self, cached_file: Path | str, *, online: bool) -> None: - self.online = online - if self.online: - self.contents: dict[str, dict[str, str | None]] = { + github: Github | None + contents: dict[str, dict[str, str | None]] + + def __init__(self, cached_file: Path | str, *, online: str | None) -> None: + if online is not None: + self.github = Github(online) + self.contents = { "setup.py": {}, "setup.cfg": {}, "pyproject.toml": {}, } else: + self.github = None with open(cached_file) as f: self.contents = yaml.safe_load(f) def get(self, repo: str, filename: str) -> str | None: - if self.online: + if self.github: try: - self.contents[filename][repo] = ( - GhApi(*repo.split("/")).get_content(filename).decode() - ) - except HTTP404NotFoundError: + gh_file = self.github.get_repo(repo).get_contents(filename) + except GithubException: self.contents[filename][repo] = None + else: + assert not isinstance(gh_file, list) + self.contents[filename][repo] = gh_file.decoded_content.decode(encoding="utf-8") + return self.contents[filename][repo] elif repo in self.contents[filename]: return self.contents[filename][repo] else: - raise RuntimeError( - f"Trying to access {repo}:{filename} and not in cache, rebuild cache" - ) + msg = f"Trying to access {repo}:{filename} and not in cache, rebuild cache" + raise RuntimeError(msg) def save(self, filename: Path | str) -> None: with open(filename, "w") as f: @@ -87,8 +105,8 @@ class MaybeRemote: @click.command() -@click.option("--online", is_flag=True, help="Remember to set GITHUB_TOKEN") -def main(online: bool) -> None: +@click.option("--online", help="Set to $GITHUB_TOKEN") +def main(online: str | None) -> None: with open(DIR / "../docs/data/projects.yml") as f: known = yaml.safe_load(f) diff --git a/bin/run_example_ci_configs.py b/bin/run_example_ci_configs.py index b96dfd2d..9cd28daa 100755 --- a/bin/run_example_ci_configs.py +++ b/bin/run_example_ci_configs.py @@ -81,7 +81,8 @@ def ci_service_for_config_file(config_file): if service.name == service_name: return service - raise ValueError(f"unknown ci service for config file {config_file}") + msg = f"unknown ci service for config file {config_file}" + raise ValueError(msg) @click.command() @@ -100,7 +101,8 @@ def run_example_ci_configs(config_files=None): for config_file in config_files: service = ci_service_for_config_file(config_file) if service.name in configs_by_service: - raise Exception("You cannot specify more than one config per CI service") + msg = "You cannot specify more than one config per CI service" + raise Exception(msg) configs_by_service[service.name] = config_file if git_repo_has_changes(): diff --git a/bin/update_how_it_works_image.py b/bin/update_how_it_works_image.py new file mode 100644 index 00000000..b7b433fb --- /dev/null +++ b/bin/update_how_it_works_image.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +try: + from html2image import Html2Image # type: ignore[import] +except ImportError: + sys.exit( + """ + html2image not found. Ensure you have Chrome (on Mac/Windows) or + Chromium (on Linux) installed, and then do: + pip install html2image + """ + ) + + +def main(): + subprocess.run(["mkdocs", "build"]) + + hti = Html2Image(custom_flags=["--force-device-scale-factor=2"]) + + html_str = Path("docs/diagram.md").read_text() + css_tags = f""" + + + + + """ + html_str = css_tags + html_str + + [screenshot, *_] = hti.screenshot( + html_str=html_str, + size=(830, 405), + ) + + dest_path = Path("docs/data/how-it-works.png") + if dest_path.exists(): + dest_path.unlink() + + Path(screenshot).rename(dest_path) + + +if __name__ == "__main__": + main() diff --git a/bin/update_pythons.py b/bin/update_pythons.py index 6dac0b76..0227c361 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -141,7 +141,8 @@ class PyPyVersions: releases = [r for r in releases if self.get_arch_file(r)] if not releases: - raise RuntimeError(f"PyPy Win {self.arch} not found for {spec}! {self.releases}") + msg = f"PyPy Win {self.arch} not found for {spec}! {self.releases}" + raise RuntimeError(msg) version_arch = "win32" if self.arch == "32" else "win_amd64" @@ -159,13 +160,15 @@ class PyPyVersions: def update_version_macos(self, spec: Specifier) -> ConfigMacOS: if self.arch != "64": - raise RuntimeError("Other archs not supported yet on macOS") + msg = "Other archs not supported yet on macOS" + raise RuntimeError(msg) releases = [r for r in self.releases if spec.contains(r["python_version"])] releases = sorted(releases, key=lambda r: r["pypy_version"]) # type: ignore[no-any-return] if not releases: - raise RuntimeError(f"PyPy macOS {self.arch} not found for {spec}!") + msg = f"PyPy macOS {self.arch} not found for {spec}!" + raise RuntimeError(msg) release = releases[-1] version = release["python_version"] diff --git a/cibuildwheel/__init__.py b/cibuildwheel/__init__.py index a4f6f370..cc406c56 100644 --- a/cibuildwheel/__init__.py +++ b/cibuildwheel/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "2.9.0" +__version__ = "2.10.2" diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index f3b44261..1a9f296f 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -6,6 +6,7 @@ import shutil import sys import tarfile import textwrap +import typing from pathlib import Path from tempfile import mkdtemp @@ -40,7 +41,7 @@ def main() -> None: parser.add_argument( "--platform", choices=["auto", "linux", "macos", "windows"], - default=os.environ.get("CIBW_PLATFORM", "auto"), + default=None, help=""" Platform to build for. Use this option to override the auto-detected platform or to run cibuildwheel on your development @@ -64,6 +65,16 @@ def main() -> None: """, ) + parser.add_argument( + "--only", + default=None, + help=""" + Force a single wheel build when given an identifier. Overrides + CIBW_BUILD/CIBW_SKIP. --platform and --arch cannot be specified + if this is given. + """, + ) + parser.add_argument( "--output-dir", type=Path, @@ -138,7 +149,8 @@ def main() -> None: try: (project_dir,) = temp_dir.iterdir() except ValueError: - raise SystemExit("invalid sdist: didn't contain a single dir") from None + msg = "invalid sdist: didn't contain a single dir" + raise SystemExit(msg) from None # This is now the new package dir args.package_dir = project_dir.resolve() @@ -154,10 +166,40 @@ def main() -> None: def build_in_directory(args: CommandLineArguments) -> None: + platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto") platform: PlatformName - if args.platform != "auto": - platform = args.platform + 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) + 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) + + platform = typing.cast(PlatformName, platform_option_value) else: ci_provider = detect_ci_provider() if ci_provider is None: @@ -187,10 +229,6 @@ def build_in_directory(args: CommandLineArguments) -> None: ) sys.exit(2) - if platform not in PLATFORMS: - print(f"cibuildwheel: Unsupported platform: {platform}", file=sys.stderr) - sys.exit(2) - options = compute_options(platform=platform, command_line_arguments=args) package_dir = options.globals.package_dir diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index 7b90ff94..a922f6f5 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -3,12 +3,19 @@ from __future__ import annotations import functools import platform as platform_module import re +import sys from enum import Enum from .typing import Final, Literal, PlatformName, assert_never PRETTY_NAMES: Final = {"linux": "Linux", "macos": "macOS", "windows": "Windows"} +ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [ + {"linux": "x86_64", "macos": "x86_64", "windows": "AMD64"}, + {"linux": "i686", "macos": None, "windows": "x86"}, + {"linux": "aarch64", "macos": "arm64", "windows": "ARM64"}, +] + @functools.total_ordering class Architecture(Enum): @@ -56,14 +63,37 @@ class Architecture(Enum): @staticmethod def auto_archs(platform: PlatformName) -> set[Architecture]: - native_architecture = Architecture(platform_module.machine()) + native_machine = platform_module.machine() + + # Cross-platform support. Used for --print-build-identifiers or docker builds. + host_platform: PlatformName = ( + "windows" + if sys.platform.startswith("win") + else ("macos" if sys.platform.startswith("darwin") else "linux") + ) + + native_architecture = Architecture(native_machine) + + # we might need to rename the native arch to the machine we're running + # on, as the same arch can have different names on different platforms + if host_platform != platform: + for arch_synonym in ARCH_SYNONYMS: + if native_machine == arch_synonym.get(host_platform): + synonym = arch_synonym[platform] + + if synonym is None: + # can't build anything on this platform + return set() + + native_architecture = Architecture(synonym) + result = {native_architecture} - if platform == "linux" and native_architecture == Architecture.x86_64: + if platform == "linux" and Architecture.x86_64 in result: # x86_64 machines can run i686 containers result.add(Architecture.i686) - if platform == "windows" and native_architecture == Architecture.AMD64: + if platform == "windows" and Architecture.AMD64 in result: result.add(Architecture.x86) return result diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py index 47e4aea3..01093a62 100644 --- a/cibuildwheel/bashlex_eval.py +++ b/cibuildwheel/bashlex_eval.py @@ -32,7 +32,8 @@ def evaluate( command_node = bashlex.parsesingle(value) if len(command_node.parts) != 1: - raise ValueError(f'"{value}" has too many parts') + msg = f"{value!r} has too many parts" + raise ValueError(msg) value_word_node = command_node.parts[0] @@ -54,7 +55,8 @@ def evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str: elif node.kind == "parameter": return evaluate_parameter_node(node, context=context) else: - raise ValueError(f'Unsupported bash construct: "{node.kind}"') + msg = f"Unsupported bash construct: {node.kind!r}" + raise ValueError(msg) def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str: @@ -65,10 +67,8 @@ def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> part_value = evaluate_node(part, context=context) if part_string not in value: - raise RuntimeError( - f'bash parse failed. part "{part_string}" not found in "{value}". ' - f'Word was "{node.word}". Full input was "{context.input}"' - ) + msg = f"bash parse failed. part {part_string!r} not found in {value!r}. Word was {node.word!r}. Full input was {context.input!r}" + raise RuntimeError(msg) value = value.replace(part_string, part_value, 1) @@ -95,9 +95,11 @@ def evaluate_nodes_as_compound_command( result += evaluate_command_node(node, context=context) elif node.kind == "operator": if node.op != ";": - raise ValueError(f'Unsupported bash operator: "{node.op}"') + msg = f"Unsupported bash operator: {node.op!r}" + raise ValueError(msg) else: - raise ValueError(f'Unsupported bash node in compound command: "{node.kind}"') + msg = f"Unsupported bash node in compound command: {node.kind!r}" + raise ValueError(msg) return result diff --git a/cibuildwheel/environment.py b/cibuildwheel/environment.py index a544852a..23ea9877 100644 --- a/cibuildwheel/environment.py +++ b/cibuildwheel/environment.py @@ -4,6 +4,7 @@ import dataclasses from typing import Any, Mapping, Sequence import bashlex +import bashlex.errors from cibuildwheel.typing import Protocol @@ -33,7 +34,11 @@ def split_env_items(env_string: str) -> list[str]: if not env_string: return [] - command_node = bashlex.parsesingle(env_string) + try: + command_node = bashlex.parsesingle(env_string) + except bashlex.errors.ParsingError as e: + raise EnvironmentParseError(env_string) from e + result = [] for word_node in command_node.parts: diff --git a/cibuildwheel/functools_cached_property_38.py b/cibuildwheel/functools_cached_property_38.py index cbe4537a..879cdb62 100644 --- a/cibuildwheel/functools_cached_property_38.py +++ b/cibuildwheel/functools_cached_property_38.py @@ -21,10 +21,8 @@ class cached_property(Generic[_T]): if self.attrname is None: self.attrname = name elif name != self.attrname: - raise TypeError( - "Cannot assign the same cached_property to two different names " - f"({self.attrname!r} and {name!r})." - ) + msg = f"Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r})." + raise TypeError(msg) @overload def __get__(self, instance: None, owner: type[Any] | None = ...) -> cached_property[_T]: @@ -38,9 +36,8 @@ class cached_property(Generic[_T]): if instance is None: return self if self.attrname is None: - raise TypeError( - "Cannot use cached_property instance without calling __set_name__ on it." - ) + msg = "Cannot use cached_property instance without calling __set_name__ on it." + raise TypeError(msg) try: cache = instance.__dict__ except AttributeError: # not all objects have __dict__ (e.g. class defines slots) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 5720074d..a64c829e 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -367,7 +367,8 @@ def build(options: Options, tmp_path: Path) -> None: # pylint: disable=unused-a cwd = Path.cwd() abs_package_dir = options.globals.package_dir.resolve() if cwd != abs_package_dir and cwd not in abs_package_dir.parents: - raise Exception("package_dir must be inside the working directory") + msg = "package_dir must be inside the working directory" + raise Exception(msg) container_project_path = PurePosixPath("/project") container_package_dir = container_project_path / abs_package_dir.relative_to(cwd) diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 04d80413..70f22997 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -204,14 +204,16 @@ def build_description_from_identifier(identifier: str) -> str: elif python_interpreter == "pp": build_description += "PyPy" else: - raise Exception("unknown python") + msg = f"unknown python {python_interpreter!r}" + raise Exception(msg) build_description += f" {python_version[0]}.{python_version[1:]} " try: build_description += PLATFORM_IDENTIFIER_DESCRIPTIONS[platform_identifier] except KeyError as e: - raise Exception("unknown platform") from e + msg = f"unknown platform {platform_identifier!r}" + raise Exception(msg) from e return build_description diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index f15a04f8..db863504 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -146,7 +146,8 @@ def setup_python( elif implementation_id.startswith("pp"): base_python = install_pypy(tmp, python_configuration.url) else: - raise ValueError("Unknown Python implementation") + msg = "Unknown Python implementation" + raise ValueError(msg) assert base_python.exists() log.step("Setting up build environment...") @@ -466,7 +467,8 @@ def build(options: Options, tmp_path: Path) -> None: ) ) else: - raise RuntimeError("unreachable") + msg = "unreachable" + raise RuntimeError(msg) # skip this test continue diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 91714c7e..c0c0cc9d 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -58,7 +58,8 @@ class OCIContainer: engine: ContainerEngine = "docker", ): if not image: - raise ValueError("Must have a non-empty image to run.") + msg = "Must have a non-empty image to run." + raise ValueError(msg) self.image = image self.simulate_32_bit = simulate_32_bit diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 241f0e0e..cb681fa3 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -10,7 +10,7 @@ from configparser import ConfigParser from contextlib import contextmanager from dataclasses import asdict, dataclass from pathlib import Path -from typing import Any, Dict, Generator, Iterator, List, Mapping, Union, cast +from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Union, cast if sys.version_info >= (3, 11): import tomllib @@ -23,7 +23,7 @@ from .architecture import Architecture from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .oci_container import ContainerEngine from .projectfiles import get_requires_python_str -from .typing import PLATFORMS, Literal, PlatformName, TypedDict +from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict from .util import ( MANYLINUX_ARCHS, MUSLLINUX_ARCHS, @@ -42,9 +42,10 @@ from .util import ( @dataclass class CommandLineArguments: - platform: Literal["auto", "linux", "macos", "windows"] + platform: Literal["auto", "linux", "macos", "windows"] | None archs: str | None output_dir: Path + only: str | None config_file: str package_dir: Path print_build_identifiers: bool @@ -122,6 +123,7 @@ DISALLOWED_OPTIONS = { class TableFmt(TypedDict): item: str sep: str + quote: NotRequired[Callable[[str], str]] class ConfigOptionError(KeyError): @@ -136,7 +138,8 @@ def _dig_first(*pairs: tuple[Mapping[str, Setting], str], ignore_empty: bool = F _dig_first((dict1, "key1"), (dict2, "key2"), ...) """ if not pairs: - raise ValueError("pairs cannot be empty") + msg = "pairs cannot be empty" + raise ValueError(msg) for dict_like, key in pairs: if key in dict_like: @@ -206,13 +209,15 @@ class OptionsReader: if config_overrides is not None: if not isinstance(config_overrides, list): - raise ConfigOptionError("'tool.cibuildwheel.overrides' must be a list") + msg = "'tool.cibuildwheel.overrides' must be a list" + raise ConfigOptionError(msg) for config_override in config_overrides: select = config_override.pop("select", None) if not select: - raise ConfigOptionError("'select' must be set in an override") + msg = "'select' must be set in an override" + raise ConfigOptionError(msg) if isinstance(select, list): select = " ".join(select) @@ -326,14 +331,16 @@ class OptionsReader: if isinstance(result, dict): if table is None: - raise ConfigOptionError(f"{name!r} does not accept a table") + msg = f"{name!r} does not accept a table" + raise ConfigOptionError(msg) return table["sep"].join( - item for k, v in result.items() for item in _inner_fmt(k, v, table["item"]) + item for k, v in result.items() for item in _inner_fmt(k, v, table) ) if isinstance(result, list): if sep is None: - raise ConfigOptionError(f"{name!r} does not accept a list") + msg = f"{name!r} does not accept a list" + raise ConfigOptionError(msg) return sep.join(result) if isinstance(result, int): @@ -342,14 +349,16 @@ class OptionsReader: return result -def _inner_fmt(k: str, v: Any, table_item: str) -> Iterator[str]: +def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]: + quote_function = table.get("quote", lambda a: a) + if isinstance(v, list): for inner_v in v: - qv = shlex.quote(inner_v) - yield table_item.format(k=k, v=qv) + qv = quote_function(inner_v) + yield table["item"].format(k=k, v=qv) else: - qv = shlex.quote(v) - yield table_item.format(k=k, v=qv) + qv = quote_function(v) + yield table["item"].format(k=k, v=qv) class Options: @@ -403,6 +412,15 @@ class Options: ) requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str) + archs_config_str = args.archs or self.reader.get("archs", sep=" ") + architectures = Architecture.parse_config(archs_config_str, platform=self.platform) + + # Process `--only` + if args.only: + build_config = args.only + skip_config = "" + architectures = Architecture.all_archs(self.platform) + build_selector = BuildSelector( build_config=build_config, skip_config=skip_config, @@ -411,9 +429,6 @@ class Options: ) test_selector = TestSelector(skip_config=test_skip) - archs_config_str = args.archs or self.reader.get("archs", sep=" ") - architectures = Architecture.parse_config(archs_config_str, platform=self.platform) - container_engine_str = self.reader.get("container-engine") if container_engine_str not in ["docker", "podman"]: @@ -442,13 +457,13 @@ class Options: build_frontend_str = self.reader.get("build-frontend", env_plat=False) environment_config = self.reader.get( - "environment", table={"item": "{k}={v}", "sep": " "} + "environment", table={"item": '{k}="{v}"', "sep": " "} ) environment_pass = self.reader.get("environment-pass", sep=" ").split() before_build = self.reader.get("before-build", sep=" && ") repair_command = self.reader.get("repair-wheel-command", sep=" && ") config_settings = self.reader.get( - "config-settings", table={"item": "{k}={v}", "sep": " "} + "config-settings", table={"item": "{k}={v}", "sep": " ", "quote": shlex.quote} ) dependency_versions = self.reader.get("dependency-versions") @@ -588,6 +603,9 @@ class Options: ] build_option_defaults = self.build_options(identifier=None) + build_options_for_identifier = { + identifier: self.build_options(identifier) for identifier in identifiers + } for option_name, default_value in sorted(asdict(build_option_defaults).items()): if option_name == "globals": @@ -597,7 +615,7 @@ class Options: # if any identifiers have an overridden value, print that too for identifier in identifiers: - option_value = getattr(self.build_options(identifier=identifier), option_name) + option_value = getattr(build_options_for_identifier[identifier], option_name) if option_value != default_value: lines.append(f" {identifier}: {option_value!r}") diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml index ff6b5ea7..d4b210ce 100644 --- a/cibuildwheel/resources/build-platforms.toml +++ b/cibuildwheel/resources/build-platforms.toml @@ -81,12 +81,12 @@ 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.6/python-3.10.6-macos11.pkg" }, - { identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.6/python-3.10.6-macos11.pkg" }, - { identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.6/python-3.10.6-macos11.pkg" }, - { identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc1-macos11.pkg" }, - { identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc1-macos11.pkg" }, - { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc1-macos11.pkg" }, + { identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.7/python-3.10.7-macos11.pkg" }, + { identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.7/python-3.10.7-macos11.pkg" }, + { identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.7/python-3.10.7-macos11.pkg" }, + { identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc2-macos11.pkg" }, + { identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc2-macos11.pkg" }, + { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0rc2-macos11.pkg" }, { identifier = "pp37-macosx_x86_64", version = "3.7", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2" }, { identifier = "pp38-macosx_x86_64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.9-osx64.tar.bz2" }, { identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.9-osx64.tar.bz2" }, @@ -102,13 +102,13 @@ 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.6", arch = "32" }, - { identifier = "cp310-win_amd64", version = "3.10.6", arch = "64" }, - { identifier = "cp311-win32", version = "3.11.0-rc1", arch = "32" }, - { identifier = "cp311-win_amd64", version = "3.11.0-rc1", arch = "64" }, + { identifier = "cp310-win32", version = "3.10.7", arch = "32" }, + { identifier = "cp310-win_amd64", version = "3.10.7", arch = "64" }, + { identifier = "cp311-win32", version = "3.11.0-rc2", arch = "32" }, + { identifier = "cp311-win_amd64", version = "3.11.0-rc2", arch = "64" }, { identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" }, - { identifier = "cp310-win_arm64", version = "3.10.6", arch = "ARM64" }, - { identifier = "cp311-win_arm64", version = "3.11.0-rc1", arch = "ARM64" }, + { identifier = "cp310-win_arm64", version = "3.10.7", arch = "ARM64" }, + { identifier = "cp311-win_arm64", version = "3.11.0-rc2", arch = "ARM64" }, { identifier = "pp37-win_amd64", version = "3.7", arch = "64", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip" }, { identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.9-win64.zip" }, { identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.9-win64.zip" }, diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index be1480a4..ec47c279 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -14,7 +14,7 @@ platformdirs==2.5.2 # via virtualenv typing-extensions==4.3.0 # via delocate -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index 51136b16..748a80a6 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -14,7 +14,7 @@ platformdirs==2.5.2 # via virtualenv typing-extensions==4.3.0 # via delocate -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints-python36.txt b/cibuildwheel/resources/constraints-python36.txt index 00956c49..41b899a8 100644 --- a/cibuildwheel/resources/constraints-python36.txt +++ b/cibuildwheel/resources/constraints-python36.txt @@ -20,7 +20,7 @@ typing-extensions==4.1.1 # via # delocate # importlib-metadata -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints-python37.txt b/cibuildwheel/resources/constraints-python37.txt index 49015dbd..076653a1 100644 --- a/cibuildwheel/resources/constraints-python37.txt +++ b/cibuildwheel/resources/constraints-python37.txt @@ -18,7 +18,7 @@ typing-extensions==4.3.0 # via # delocate # importlib-metadata -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 0eb2b244..242f07d4 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -14,7 +14,7 @@ platformdirs==2.5.2 # via virtualenv typing-extensions==4.3.0 # via delocate -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 0b7e373d..160fef75 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -14,7 +14,7 @@ platformdirs==2.5.2 # via virtualenv typing-extensions==4.3.0 # via delocate -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index 51136b16..748a80a6 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -14,7 +14,7 @@ platformdirs==2.5.2 # via virtualenv typing-extensions==4.3.0 # via delocate -virtualenv==20.16.4 +virtualenv==20.16.5 # via -r cibuildwheel/resources/constraints.in wheel==0.37.1 # via diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg index f3047929..486aae09 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:2022-09-04-d1c2903 +manylinux1 = quay.io/pypa/manylinux1_x86_64:2022-09-18-e2e56b7 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2022-09-04-870f6a2 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2022-09-18-2b8b451 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2022-09-18-2b8b451 [i686] -manylinux1 = quay.io/pypa/manylinux1_i686:2022-09-04-d1c2903 +manylinux1 = quay.io/pypa/manylinux1_i686:2022-09-18-e2e56b7 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-09-04-870f6a2 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-09-18-2b8b451 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2022-09-18-2b8b451 [pypy_x86_64] manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2022-09-18-2b8b451 [pypy_i686] manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-09-18-2b8b451 [aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2022-09-04-870f6a2 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2022-09-18-2b8b451 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2022-09-18-2b8b451 [ppc64le] -manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2022-09-04-870f6a2 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2022-09-18-2b8b451 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2022-09-18-2b8b451 [s390x] -manylinux2014 = quay.io/pypa/manylinux2014_s390x:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2022-09-04-870f6a2 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_s390x:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2022-09-18-2b8b451 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2022-09-18-2b8b451 [pypy_aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2022-09-04-870f6a2 -manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-09-04-870f6a2 -manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2022-09-04-870f6a2 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2022-09-18-2b8b451 +manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-09-18-2b8b451 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2022-09-18-2b8b451 diff --git a/cibuildwheel/resources/virtualenv.toml b/cibuildwheel/resources/virtualenv.toml index 1c277d73..0fbf028c 100644 --- a/cibuildwheel/resources/virtualenv.toml +++ b/cibuildwheel/resources/virtualenv.toml @@ -1,2 +1,2 @@ -version = "20.16.4" -url = "https://github.com/pypa/get-virtualenv/blob/20.16.4/public/virtualenv.pyz?raw=true" +version = "20.16.5" +url = "https://github.com/pypa/get-virtualenv/blob/20.16.5/public/virtualenv.pyz?raw=true" diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index a546d7bf..3ffa5476 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -10,6 +10,10 @@ if sys.version_info < (3, 8): else: from typing import Final, Literal, OrderedDict, Protocol, TypedDict +if sys.version_info < (3, 11): + from typing_extensions import NotRequired +else: + from typing import NotRequired __all__ = ( "Final", @@ -26,6 +30,7 @@ __all__ = ( "OrderedDict", "Union", "assert_never", + "NotRequired", ) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 4b3d4fb9..01db05ec 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -63,13 +63,13 @@ __all__ = [ "split_config_settings", ] -resources_dir: Final = Path(__file__).parent / "resources" +resources_dir: Final[Path] = Path(__file__).parent / "resources" -install_certifi_script: Final = resources_dir / "install_certifi.py" +install_certifi_script: Final[Path] = resources_dir / "install_certifi.py" BuildFrontend = Literal["pip", "build"] -MANYLINUX_ARCHS: Final = ( +MANYLINUX_ARCHS: Final[tuple[str, ...]] = ( "x86_64", "i686", "pypy_x86_64", @@ -80,7 +80,7 @@ MANYLINUX_ARCHS: Final = ( "pypy_i686", ) -MUSLLINUX_ARCHS: Final = ( +MUSLLINUX_ARCHS: Final[tuple[str, ...]] = ( "x86_64", "i686", "aarch64", @@ -88,10 +88,12 @@ MUSLLINUX_ARCHS: Final = ( "s390x", ) -DEFAULT_CIBW_CACHE_PATH: Final = user_cache_path(appname="cibuildwheel", appauthor="pypa") -CIBW_CACHE_PATH: Final = Path(os.environ.get("CIBW_CACHE_PATH", DEFAULT_CIBW_CACHE_PATH)).resolve() +DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa") +CIBW_CACHE_PATH: Final[Path] = Path( + os.environ.get("CIBW_CACHE_PATH", DEFAULT_CIBW_CACHE_PATH) +).resolve() -IS_WIN: Final = sys.platform.startswith("win") +IS_WIN: Final[bool] = sys.platform.startswith("win") @overload @@ -145,7 +147,7 @@ def shell(*commands: str, env: dict[str, str] | None = None, cwd: PathOrStr | No subprocess.run(command, env=env, cwd=cwd, shell=True, check=True) -def format_safe(template: str, **kwargs: Any) -> str: +def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str: """ Works similarly to `template.format(**kwargs)`, except that unmatched fields in `template` are passed through untouched. @@ -173,11 +175,9 @@ def format_safe(template: str, **kwargs: Any) -> str: re.VERBOSE, ) - # we use a function for repl to prevent re.sub interpreting backslashes - # in repl as escape sequences. result = re.sub( pattern=find_pattern, - repl=lambda _: str(value), # pylint: disable=cell-var-from-loop + repl=str(value).replace("\\", r"\\"), string=result, ) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index d256c27c..ede27bb5 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -235,7 +235,8 @@ def setup_python( assert python_configuration.url is not None base_python = install_pypy(tmp, python_configuration.arch, python_configuration.url) else: - raise ValueError("Unknown Python implementation") + msg = "Unknown Python implementation" + raise ValueError(msg) assert base_python.exists() log.step("Setting up build environment...") diff --git a/docs/changelog.md b/docs/changelog.md index 2b465a91..8ed60e82 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,32 @@ title: Changelog # Changelog +### v2.10.2 + +_25 September 2022_ + +- 🐛 Fix a bug that caused `win32` identifiers to fail when used with `--only`. (#1282) +- 🐛 Fix computation of `auto`/`auto64`/`auto32` archs when targeting a different platform to the one that you're running cibuildwheel on. (#1266) +- 📚 Fix an mistake in the 'how it works' diagram. (#1274) + +### v2.10.1 + +_18 September 2022_ + +- 🐛 Fix a bug that stopped environment variables specified in TOML from being expanded. (#1273) + +### v2.10.0 + +_13 September 2022_ + +- 🌟 Adds support for [building wheels on Cirrus CI](https://cibuildwheel.readthedocs.io/en/stable/setup/#cirrus-ci). This is exciting for us, as it's the first public CI platform that natively supports macOS Apple Silicon (aka. M1, `arm64`) runners. As such, it's the first platform that you can natively build _and test_ macOS `arm64` wheels. It also has native Linux ARM (aarch64) runners, for fast, native builds there. (#1191) +- 🌟 Adds support for running cibuildwheel on Apple Silicon machines. For a while, we've supported cross-compilation of Apple Silicon wheels on `x86_64`, but now that we have Cirrus CI we can run our test suite and officially support running cibuildwheel on `arm64`. (#1191) +- ✨ Adds the `--only` [command line option](https://cibuildwheel.readthedocs.io/en/stable/options/#command-line), to specify a single build to run. Previously, it could be cumbersome to set all the build selection options to target a specific build - for example, you might have to run something like `CIBW_BUILD=cp39-manylinux_x86_64 cibuildwheel --platform linux --archs x86_64`. The new `--only` option overrides all the build selection options to simplify running a single build, which now looks like `cibuildwheel --only cp39-manylinux_x86_64`. (#1098) +- ✨ Adds the [`CIBW_CONFIG_SETTINGS`](https://cibuildwheel.readthedocs.io/en/stable/options/#config-settings) option, so you can pass arguments to your package's build backend (#1244) +- 🛠 Updates the CPython 3.11 version to the latest release candidate - v3.11.0rc2. (#1265) +- 🐛 Fix a bug that can cause a RecursionError on Windows when building from an sdist. (#1253) +- 🛠 Add support for the s390x architecture on manylinux_2_28 (#1255) + ### v2.9.0 _11 August 2022_ @@ -29,9 +55,10 @@ _5 July 2022_ - ✨ You can now run cibuildwheel on Podman, as an alternate container engine to Docker (which remains the default). This is useful in environments where a Docker daemon isn't available, for example, it can be run inside a Docker container, or without root access. To use Podman, set the [`CIBW_CONTAINER_ENGINE`](https://cibuildwheel.readthedocs.io/en/stable/options/#container-engine) option. (#966) - ✨ Adds support for building `py3-none-{platform}` wheels. This works the same as ABI3 - wheels won't be rebuilt, but tests will still be run across all selected versions of Python. - > These wheels contain native extension code, but don't use the Python APIs. Typically, they're bridged to Python using a FFI module like [ctypes](https://docs.python.org/3/library/ctypes.html) or [cffi](https://cffi.readthedocs.io/en/latest/). Because they don't use Python ABI, the wheels are more compatible - they work across many Python versions. + These wheels contain native extension code, but don't use the Python APIs. Typically, they're bridged to Python using a FFI module like [ctypes](https://docs.python.org/3/library/ctypes.html) or [cffi](https://cffi.readthedocs.io/en/latest/). Because they don't use Python ABI, the wheels are more compatible - they work across many Python versions. + + Check out this [example ctypes project](https://github.com/joerick/python-ctypes-package-sample) to see an example of how it works. (#1151) - Check out this [example ctypes project](https://github.com/joerick/python-ctypes-package-sample) to see an example of how it works. (#1151) - 🛠 cibuildwheel will now error if multiple builds in a single run produce the same wheel filename, as this indicates a misconfiguration. (#1152) - 📚 A few docs improvements and updates to keep things up-to-date. @@ -716,6 +743,18 @@ _31 March 2017_ - 🌟 First public release! + +