From 619e1dc98929aeea9106e412c2b5e2a3a1f90b3a Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Wed, 4 Feb 2026 09:26:35 +0000 Subject: [PATCH 01/48] WIP - initial punt at audit command --- cibuildwheel/__main__.py | 13 ++ cibuildwheel/audit.py | 89 ++++++++++++ cibuildwheel/options.py | 4 + .../resources/cibuildwheel.schema.json | 15 ++ cibuildwheel/resources/defaults.toml | 1 + unit_test/audit_test.py | 134 ++++++++++++++++++ unit_test/options_toml_test.py | 43 ++++++ 7 files changed, 299 insertions(+) create mode 100644 cibuildwheel/audit.py create mode 100644 unit_test/audit_test.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 72e2434bc..bec7ae051 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -384,10 +384,23 @@ def build_in_directory(args: CommandLineArguments) -> None: output_dir.mkdir(parents=True, exist_ok=True) + # Snapshot wheels before build to detect newly built ones + wheels_before = {p.name for p in output_dir.glob("*.whl")} + tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True) try: with log.print_summary(options=options): platform_module.build(options, tmp_path) + + # Run audit step after all builds complete + if options.globals.audit_command: + from cibuildwheel.audit import run_audit + + run_audit( + audit_command=options.globals.audit_command, + output_dir=output_dir, + wheels_before=wheels_before, + ) finally: # avoid https://github.com/python/cpython/issues/86962 by performing # cleanup manually diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py new file mode 100644 index 000000000..65f9cbd25 --- /dev/null +++ b/cibuildwheel/audit.py @@ -0,0 +1,89 @@ +""" +Audit step for wheels built by cibuildwheel. + +This module provides functionality to run audit commands (like abi3audit) +on built wheels after all platform builds are complete. +""" + +import os +import subprocess +from pathlib import Path + +from packaging.utils import parse_wheel_filename + +from .logger import log +from .util.helpers import format_safe + + +def is_abi3_wheel(wheel_path: Path) -> bool: + """Check if a wheel is an abi3 wheel by parsing its filename.""" + _, _, _, tags = parse_wheel_filename(wheel_path.name) + return any(t.abi == "abi3" for t in tags) + + +def run_audit( + audit_command: str, + output_dir: Path, + wheels_before: set[str], +) -> None: + """ + Run the audit command on wheels built in this run. + + The audit command supports the following placeholders: + - {wheel}: expands to each wheel path, runs the command once per wheel + - {abi3_wheel}: same as {wheel}, but only for abi3 wheels + + If the command contains {abi3_wheel} but no abi3 wheels were produced, + the audit step is skipped. + + Args: + audit_command: The command template to run + output_dir: Directory where wheels were output + wheels_before: Set of wheel filenames that existed before the build + """ + if not audit_command: + return + + # Find wheels built in this run (new wheels that weren't there before) + all_wheels = sorted(output_dir.glob("*.whl")) + just_built = [w for w in all_wheels if w.name not in wheels_before] + + if not just_built: + return + + # Determine if we're auditing abi3 wheels only + abi3_only = "{abi3_wheel}" in audit_command + + # Filter wheels if needed + if abi3_only: + wheels_to_audit = [w for w in just_built if is_abi3_wheel(w)] + if not wheels_to_audit: + log.step("Skipping audit step (no abi3 wheels produced)") + return + else: + wheels_to_audit = just_built + + log.step("Running audit...") + + for wheel in wheels_to_audit: + # Prepare command with placeholders + prepared = format_safe( + audit_command, + wheel=wheel, + abi3_wheel=wheel, + ) + + log.step(f" Auditing {wheel.name}...") + env = os.environ.copy() + + try: + subprocess.run( + prepared, + shell=True, + check=True, + env=env, + cwd=output_dir, + ) + except subprocess.CalledProcessError as e: + log.error(f"Audit command failed for {wheel.name}") + raise SystemExit(e.returncode) from e diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index af946a883..133f8cc7a 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -90,6 +90,7 @@ class GlobalOptions: test_selector: TestSelector architectures: set[Architecture] allow_empty: bool + audit_command: str @dataclasses.dataclass(frozen=True) @@ -695,6 +696,8 @@ def globals(self) -> GlobalOptions: ) test_selector = TestSelector(skip_config=test_skip) + audit_command = self.reader.get("audit", option_format=ListFormat(sep=" && ")) + return GlobalOptions( package_dir=package_dir, output_dir=output_dir, @@ -702,6 +705,7 @@ def globals(self) -> GlobalOptions: test_selector=test_selector, architectures=architectures, allow_empty=allow_empty, + audit_command=audit_command, ) def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None: diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index c5a27ea8c..c543ae42c 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -27,6 +27,21 @@ "description": "cibuildwheel's settings.", "type": "object", "properties": { + "audit": { + "description": "Execute a shell command to audit each wheel after all builds complete. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "title": "CIBW_AUDIT" + }, "archs": { "description": "Change the architectures built on your machine by default.", "oneOf": [ diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 78895bf9a..a58952d8d 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -5,6 +5,7 @@ test-skip = "" enable = [] archs = ["auto"] +audit = "" build-frontend = "default" config-settings = {} dependency-versions = "pinned" diff --git a/unit_test/audit_test.py b/unit_test/audit_test.py new file mode 100644 index 000000000..bf1ef5c67 --- /dev/null +++ b/unit_test/audit_test.py @@ -0,0 +1,134 @@ +from pathlib import Path + +import pytest + +from cibuildwheel.audit import is_abi3_wheel, run_audit + + +class TestIsAbi3Wheel: + def test_abi3_wheel(self) -> None: + assert is_abi3_wheel(Path("example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl")) + + def test_abi3_wheel_macos(self) -> None: + assert is_abi3_wheel(Path("example-1.0.0-cp39-abi3-macosx_10_9_x86_64.whl")) + + def test_abi3_wheel_windows(self) -> None: + assert is_abi3_wheel(Path("example-1.0.0-cp310-abi3-win_amd64.whl")) + + def test_non_abi3_wheel(self) -> None: + assert not is_abi3_wheel(Path("example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")) + + def test_pure_python_wheel(self) -> None: + assert not is_abi3_wheel(Path("example-1.0.0-py3-none-any.whl")) + + +class TestRunAudit: + def test_empty_command_does_nothing(self, tmp_path: Path) -> None: + # Create a wheel file + wheel_path = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Should not raise and should be a no-op + run_audit( + audit_command="", + output_dir=tmp_path, + wheels_before=set(), + ) + + def test_audit_runs_on_new_wheels(self, tmp_path: Path) -> None: + # Create a wheel file (simulating a build) + wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Create a marker file to verify command ran + marker = tmp_path / "audit_ran.txt" + + run_audit( + audit_command=f"touch {marker}", + output_dir=tmp_path, + wheels_before=set(), + ) + + assert marker.exists() + + def test_audit_skips_old_wheels(self, tmp_path: Path) -> None: + # Create a wheel file + wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Create a marker file to verify command ran + marker = tmp_path / "audit_ran.txt" + + # Pre-existing wheel should be skipped + run_audit( + audit_command=f"touch {marker}", + output_dir=tmp_path, + wheels_before={wheel_path.name}, + ) + + assert not marker.exists() + + def test_abi3_only_mode_skips_non_abi3(self, tmp_path: Path) -> None: + # Create a non-abi3 wheel + wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Create a marker file to verify command ran + marker = tmp_path / "audit_ran.txt" + + run_audit( + audit_command=f"echo {{abi3_wheel}} && touch {marker}", + output_dir=tmp_path, + wheels_before=set(), + ) + + # Should not run because no abi3 wheels + assert not marker.exists() + + def test_abi3_only_mode_runs_on_abi3(self, tmp_path: Path) -> None: + # Create an abi3 wheel + wheel_path = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Create a marker file to verify command ran + marker = tmp_path / "audit_ran.txt" + + run_audit( + audit_command=f"echo {{abi3_wheel}} && touch {marker}", + output_dir=tmp_path, + wheels_before=set(), + ) + + assert marker.exists() + + def test_wheel_placeholder_expanded(self, tmp_path: Path) -> None: + # Create a wheel file + wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + # Write wheel path to a file to verify expansion + output_file = tmp_path / "wheel_path.txt" + + run_audit( + audit_command=f"echo {{wheel}} > {output_file}", + output_dir=tmp_path, + wheels_before=set(), + ) + + assert output_file.exists() + content = output_file.read_text().strip() + assert content == str(wheel_path) + + def test_audit_fails_on_error(self, tmp_path: Path) -> None: + # Create a wheel file + wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + wheel_path.touch() + + with pytest.raises(SystemExit) as exc_info: + run_audit( + audit_command="exit 1", + output_dir=tmp_path, + wheels_before=set(), + ) + + assert exc_info.value.code == 1 diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index 0fb19f638..255cec2c8 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -541,3 +541,46 @@ def test_overrides_inherit(tmp_path): options_reader.get("config-settings", option_format=ShlexTableFormat()) == "key1=value1 key2=override2 empty='' key3=value3" ) + + +def test_audit_option(tmp_path, platform): + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit = "abi3audit {abi3_wheel}" +""" + ) + + options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) + assert options_reader.get("audit", option_format=ListFormat(" && ")) == "abi3audit {abi3_wheel}" + + +def test_audit_option_list(tmp_path, platform): + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit = ["first command", "second command"] +""" + ) + + options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) + assert ( + options_reader.get("audit", option_format=ListFormat(" && ")) + == "first command && second command" + ) + + +def test_audit_option_env(tmp_path, platform): + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +""" + ) + + options_reader = OptionsReader( + pyproject_toml, platform=platform, env={"CIBW_AUDIT": "my-audit-tool {wheel}"} + ) + assert options_reader.get("audit", option_format=ListFormat(" && ")) == "my-audit-tool {wheel}" From 322f2c412e30fdc2033acc338b376c6d52d69210 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:59:37 +0530 Subject: [PATCH 02/48] Add `abi3audit` as a dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b252fdc77..e878c2379 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ "Topic :: Software Development :: Build Tools", ] dependencies = [ + "abi3audit", "bashlex!=0.13", "bracex", "build>=1.0.0", From f917a2495d5339745d73c20bbc7546dd57346bdf Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:01:38 +0530 Subject: [PATCH 03/48] Add helper functions to check stable ABI wheels --- cibuildwheel/util/packaging.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cibuildwheel/util/packaging.py b/cibuildwheel/util/packaging.py index c3d8c21dd..7f419bc0f 100644 --- a/cibuildwheel/util/packaging.py +++ b/cibuildwheel/util/packaging.py @@ -1,4 +1,6 @@ import shlex +import subprocess +import sys from collections.abc import Mapping, Sequence from dataclasses import dataclass, field from pathlib import Path, PurePath @@ -6,6 +8,7 @@ from packaging.utils import parse_wheel_filename +from ..logger import log from . import resources from .cmd import call from .helpers import parse_key_value_string, unwrap @@ -178,3 +181,24 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None: return wheel return None + + +def is_abi3_wheel(wheel_name: str) -> bool: + """Check if a wheel uses the abi3 stable ABI based on its filename.""" + _, _, _, tags = parse_wheel_filename(wheel_name) + return any(tag.abi == "abi3" for tag in tags) + + +def run_abi3audit(wheel_path: Path) -> None: + """Run abi3audit on the given wheel if it is an abi3 wheel. + + Raises subprocess.CalledProcessError if abi3audit reports violations. + """ + if not is_abi3_wheel(wheel_path.name): + return + + log.step("Running abi3audit...") + subprocess.run( + [sys.executable, "-m", "abi3audit", "--strict", "--report", str(wheel_path)], + check=True, + ) From f2fdc93c8f68c804f1cc21adf7376b6987ae44d0 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:03:05 +0530 Subject: [PATCH 04/48] Run `abi3audit` for macOS and Windows wheels --- cibuildwheel/platforms/macos.py | 4 +++- cibuildwheel/platforms/windows.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 137c68964..fa5e28029 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -32,7 +32,7 @@ move_file, ) from ..util.helpers import prepare_command, unwrap -from ..util.packaging import find_compatible_wheel, get_pip_version +from ..util.packaging import find_compatible_wheel, get_pip_version, run_abi3audit from ..venv import constraint_flags, find_uv, virtualenv @@ -564,6 +564,8 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) + run_abi3audit(repaired_wheel) + log.step_end() if build_options.test_command and build_options.test_selector(config.identifier): diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index d13d925bf..b8e4ae549 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -23,7 +23,7 @@ from ..util.cmd import call, shell from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file from ..util.helpers import prepare_command, unwrap -from ..util.packaging import find_compatible_wheel, get_pip_version +from ..util.packaging import find_compatible_wheel, get_pip_version, run_abi3audit from ..venv import constraint_flags, find_uv, virtualenv @@ -549,6 +549,8 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) + run_abi3audit(repaired_wheel) + test_selected = options.globals.test_selector(config.identifier) if test_selected and config.arch == "ARM64" != platform_module.machine(): log.warning( From 10542197c5986c365f699e29ef89af17e3f3b868 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:13:07 +0530 Subject: [PATCH 05/48] Copy out of container for repairing? --- cibuildwheel/platforms/linux.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index 818ad8f08..551fb3347 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -1,5 +1,6 @@ import contextlib import dataclasses +import shutil import subprocess import sys import textwrap @@ -18,7 +19,7 @@ from ..util import resources from ..util.file import copy_test_sources from ..util.helpers import prepare_command, unwrap -from ..util.packaging import find_compatible_wheel +from ..util.packaging import find_compatible_wheel, is_abi3_wheel, run_abi3audit if TYPE_CHECKING: from ..typing import PathOrStr @@ -359,6 +360,14 @@ def build_in_container( if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) + if is_abi3_wheel(repaired_wheel.name): + local_abi3audit_dir = local_identifier_tmp_dir / "abi3audit" + local_abi3audit_dir.mkdir(exist_ok=True) + container.copy_out(repaired_wheel_dir, local_abi3audit_dir) + local_wheel = local_abi3audit_dir / repaired_wheel.name + run_abi3audit(local_wheel) + shutil.rmtree(local_abi3audit_dir) + if build_options.test_command and build_options.test_selector(config.identifier): log.step("Testing wheel...") From 03d17089e774a6b820165b2eb62dc7f3c03a33dd Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:22:08 +0530 Subject: [PATCH 06/48] Add some notes that `cibuildwheel` runs `abi3audit` --- docs/faq.md | 2 +- docs/options.md | 29 +++-------------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 949ee717f..8d9e41d3c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -33,7 +33,7 @@ The CPython Limited API is a subset of the Python C Extension API that's declare To create a package that builds ABI3 wheels, you'll need to configure your build backend to compile libraries correctly create wheels with the right tags. [Check this repo](https://github.com/joerick/python-abi3-package-sample) for an example of how to do this with setuptools. -You could also consider running [abi3audit](https://github.com/trailofbits/abi3audit) against the produced wheels in order to check for abi3 violations or inconsistencies. You can run it alongside the default in your [repair-wheel-command](options.md#repair-wheel-command). +cibuildwheel automatically runs [abi3audit](https://github.com/trailofbits/abi3audit) on any abi3 wheel after the repair step to check for stable ABI violations or inconsistencies. If abi3audit detects any issues, the build will fail with a detailed report. ### Packages with optional C extensions {: #optional-extensions} diff --git a/docs/options.md b/docs/options.md index 90f9015eb..0f2ea2517 100644 --- a/docs/options.md +++ b/docs/options.md @@ -964,24 +964,11 @@ Platform-specific environment variables are also available:
'python scripts/check_repaired_wheel.py -w {dest_dir} {wheel}', ] - # Use abi3audit to catch issues with Limited API wheels - [tool.cibuildwheel.linux] - repair-wheel-command = [ - "auditwheel repair -w {dest_dir} {wheel}", - "pipx run abi3audit --strict --report {wheel}", - ] - [tool.cibuildwheel.macos] - repair-wheel-command = [ - "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}", - "pipx run abi3audit --strict --report {wheel}", - ] - [tool.cibuildwheel.windows] - repair-wheel-command = [ - "copy {wheel} {dest_dir}", - "pipx run abi3audit --strict --report {wheel}", - ] ``` + !!! note + cibuildwheel automatically runs [abi3audit](https://github.com/trailofbits/abi3audit) on abi3 wheels after the repair step. You no longer need to add it to your repair command manually. + In configuration files, you can use an inline array, and the items will be joined with `&&`. @@ -1003,16 +990,6 @@ Platform-specific environment variables are also available:
python scripts/repair_wheel.py -w {dest_dir} {wheel} && python scripts/check_repaired_wheel.py -w {dest_dir} {wheel} - # Use abi3audit to catch issues with Limited API wheels - CIBW_REPAIR_WHEEL_COMMAND_LINUX: > - auditwheel repair -w {dest_dir} {wheel} && - pipx run abi3audit --strict --report {wheel} - CIBW_REPAIR_WHEEL_COMMAND_MACOS: > - delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} && - pipx run abi3audit --strict --report {wheel} - CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: > - copy {wheel} {dest_dir} && - pipx run abi3audit --strict --report {wheel} ``` From 97cc3c20a21da17781fc8fbcfed7961351a3279e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:22:54 +0530 Subject: [PATCH 07/48] Add basic unit tests --- unit_test/abi3audit_test.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 unit_test/abi3audit_test.py diff --git a/unit_test/abi3audit_test.py b/unit_test/abi3audit_test.py new file mode 100644 index 000000000..0807ec9a2 --- /dev/null +++ b/unit_test/abi3audit_test.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest + +from cibuildwheel.util.packaging import is_abi3_wheel, run_abi3audit + + +class TestIsAbi3Wheel: + def test_abi3_wheel(self): + assert is_abi3_wheel("foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") is True + + def test_abi3_wheel_macos(self): + assert is_abi3_wheel("foo-1.0-cp311-abi3-macosx_11_0_arm64.whl") is True + + def test_abi3_wheel_windows(self): + assert is_abi3_wheel("foo-1.0-cp310-abi3-win_amd64.whl") is True + + def test_cpython_wheel(self): + assert is_abi3_wheel("foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") is False + + def test_none_any_wheel(self): + assert is_abi3_wheel("foo-1.0-py3-none-any.whl") is False + + def test_none_platform_wheel(self): + assert is_abi3_wheel("foo-1.0-cp310-none-win_amd64.whl") is False + + +class TestRunAbi3audit: + def test_skips_non_abi3_wheel(self): + wheel = Path("/tmp/foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") + with patch("subprocess.run") as mock_run: + run_abi3audit(wheel) + mock_run.assert_not_called() + + def test_runs_on_abi3_wheel(self): + wheel = Path("/tmp/foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") + with patch("cibuildwheel.util.packaging.subprocess.run") as mock_run: + run_abi3audit(wheel) + mock_run.assert_called_once_with( + [sys.executable, "-m", "abi3audit", "--strict", "--report", str(wheel)], + check=True, + ) + + def test_raises_on_failure(self): + wheel = Path("/tmp/foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") + with ( + patch( + "cibuildwheel.util.packaging.subprocess.run", + side_effect=subprocess.CalledProcessError(1, "abi3audit"), + ), + pytest.raises(subprocess.CalledProcessError), + ): + run_abi3audit(wheel) From 97b6d517dc736d87f489e845109e03471db373ff Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:39:02 +0530 Subject: [PATCH 08/48] Add a basic C extension with `Py_LIMITED_API` --- test/test_abi3audit.py | 71 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/test_abi3audit.py diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py new file mode 100644 index 000000000..91b582c4f --- /dev/null +++ b/test/test_abi3audit.py @@ -0,0 +1,71 @@ +import textwrap + +from . import test_projects, utils + +pyproject_toml = r""" +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" +""" + +limited_api_project = test_projects.new_c_project( + setup_py_add=textwrap.dedent( + r""" + import sysconfig + + IS_CPYTHON = sys.implementation.name == "cpython" + Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED") + CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED + setup_options = {} + extension_kwargs = {} + if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10): + extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x030A0000")] + extension_kwargs["py_limited_api"] = True + setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}} + """ + ), + setup_py_extension_args_add="**extension_kwargs", + setup_py_setup_args_add="options=setup_options", +) + +limited_api_project.files["pyproject.toml"] = pyproject_toml + + +def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): + """Test that abi3audit runs automatically on abi3 wheels.""" + project_dir = tmp_path / "project" + limited_api_project.generate(project_dir) + + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + # Let's only build one cpython version to keep the test fast. + "CIBW_BUILD": "cp310-*", + "CIBW_ARCHS": "native", + }, + ) + + assert len(actual_wheels) >= 1 + + captured = capfd.readouterr() + assert "Running abi3audit" in captured.out + + +def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): + """Test that abi3audit does not run for non-abi3 wheels.""" + project_dir = tmp_path / "project" + basic_project = test_projects.new_c_project() + basic_project.generate(project_dir) + + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_ARCHS": "native", + }, + single_python=True, + ) + + assert len(actual_wheels) >= 1 + + captured = capfd.readouterr() + assert "Running abi3audit" not in captured.out From 87ac303ff4bada308e4a494d756d3d164ff67d99 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:23:35 +0530 Subject: [PATCH 09/48] Add a test project that violates Stable ABI --- test/test_abi3audit.py | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index 91b582c4f..d7f2e7ead 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -1,5 +1,8 @@ +import subprocess import textwrap +import pytest + from . import test_projects, utils pyproject_toml = r""" @@ -30,6 +33,42 @@ limited_api_project.files["pyproject.toml"] = pyproject_toml +# Project that claims abi3 but violates the stable ABI by calling +# PyUnicode_AsUTF8 (not in stable ABI until 3.13) without defining +# Py_LIMITED_API in the C code. +violating_abi3_project = test_projects.new_c_project( + setup_py_add=textwrap.dedent( + r""" + import sysconfig + + IS_CPYTHON = sys.implementation.name == "cpython" + Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED") + CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED + setup_options = {} + extension_kwargs = {} + if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10): + # Intentionally NOT defining Py_LIMITED_API as a C macro, + # but still tagging the wheel as abi3. + extension_kwargs["py_limited_api"] = True + setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}} + """ + ), + spam_c_function_add=textwrap.dedent( + r""" + // Call a function not in the stable ABI until Python 3.13. + // Without Py_LIMITED_API defined, the compiler allows it. + PyObject *str_obj = PyUnicode_FromString(content); + const char *utf8 = PyUnicode_AsUTF8(str_obj); + (void)utf8; + Py_DECREF(str_obj); + """ + ), + setup_py_extension_args_add="**extension_kwargs", + setup_py_setup_args_add="options=setup_options", +) + +violating_abi3_project.files["pyproject.toml"] = pyproject_toml + def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): """Test that abi3audit runs automatically on abi3 wheels.""" @@ -69,3 +108,25 @@ def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): captured = capfd.readouterr() assert "Running abi3audit" not in captured.out + + +def test_abi3audit_detects_violation(tmp_path, capfd): + """Test that abi3audit catches stable ABI violations and fails the build. + + This project tags the wheel as cp310-abi3 but uses PyUnicode_AsUTF8, + which was not part of the stable ABI until Python 3.13. + """ + project_dir = tmp_path / "project" + violating_abi3_project.generate(project_dir) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_BUILD": "cp310-*", + "CIBW_ARCHS": "native", + }, + ) + + captured = capfd.readouterr() + assert "Running abi3audit" in captured.out From babfb13ec480baa629461839ebca2e1a245a395d Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:27:26 +0530 Subject: [PATCH 10/48] Fix linux test --- cibuildwheel/platforms/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index 551fb3347..83149ab19 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -362,7 +362,7 @@ def build_in_container( if is_abi3_wheel(repaired_wheel.name): local_abi3audit_dir = local_identifier_tmp_dir / "abi3audit" - local_abi3audit_dir.mkdir(exist_ok=True) + local_abi3audit_dir.mkdir(parents=True, exist_ok=True) container.copy_out(repaired_wheel_dir, local_abi3audit_dir) local_wheel = local_abi3audit_dir / repaired_wheel.name run_abi3audit(local_wheel) From 8d6988df97d5d9ff95d2960fb6ed50dd6b16db73 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:03:34 +0530 Subject: [PATCH 11/48] Skip abi3 wheel tests for Pyodide --- test/test_abi3audit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index d7f2e7ead..f4c35553f 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -70,6 +70,7 @@ violating_abi3_project.files["pyproject.toml"] = pyproject_toml +@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): """Test that abi3audit runs automatically on abi3 wheels.""" project_dir = tmp_path / "project" @@ -90,6 +91,7 @@ def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): assert "Running abi3audit" in captured.out +@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): """Test that abi3audit does not run for non-abi3 wheels.""" project_dir = tmp_path / "project" @@ -110,6 +112,7 @@ def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): assert "Running abi3audit" not in captured.out +@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") def test_abi3audit_detects_violation(tmp_path, capfd): """Test that abi3audit catches stable ABI violations and fails the build. From 6e9c281d79222d89365d7cac22fd0b2e91a22f62 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:40:18 +0530 Subject: [PATCH 12/48] Patch the correct subprocess module --- unit_test/abi3audit_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_test/abi3audit_test.py b/unit_test/abi3audit_test.py index 0807ec9a2..b03c98c37 100644 --- a/unit_test/abi3audit_test.py +++ b/unit_test/abi3audit_test.py @@ -33,7 +33,7 @@ def test_none_platform_wheel(self): class TestRunAbi3audit: def test_skips_non_abi3_wheel(self): wheel = Path("/tmp/foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") - with patch("subprocess.run") as mock_run: + with patch("cibuildwheel.util.packaging.subprocess.run") as mock_run: run_abi3audit(wheel) mock_run.assert_not_called() From 597f061828a0c623f98da6fb89f230440dad5130 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:40:51 +0530 Subject: [PATCH 13/48] wrap cleanup of abi3audit dir --- cibuildwheel/platforms/linux.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index 83149ab19..635392a16 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -366,7 +366,10 @@ def build_in_container( container.copy_out(repaired_wheel_dir, local_abi3audit_dir) local_wheel = local_abi3audit_dir / repaired_wheel.name run_abi3audit(local_wheel) - shutil.rmtree(local_abi3audit_dir) + try: + run_abi3audit(local_wheel) + finally: + shutil.rmtree(local_abi3audit_dir, ignore_errors=True) if build_options.test_command and build_options.test_selector(config.identifier): log.step("Testing wheel...") From 91c394f1b138f3aa3cef04d409b3860eac41f1e4 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Wed, 1 Apr 2026 19:22:22 +0100 Subject: [PATCH 14/48] Write the docs for the new options --- README.md | 4 +- docs/options.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b42cdc4c5..48a4382f9 100644 --- a/README.md +++ b/README.md @@ -165,12 +165,14 @@ The following diagram summarises the steps that cibuildwheel takes on each platf | | [`test-skip`](https://cibuildwheel.pypa.io/en/stable/options/#test-skip) | Skip running tests on some builds | | | [`test-environment`](https://cibuildwheel.pypa.io/en/stable/options/#test-environment) | Set environment variables for the test environment | | | [`test-runtime`](https://cibuildwheel.pypa.io/en/stable/options/#test-runtime) | Controls how the tests will be executed. | +| **Auditing** | [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) | Install Python dependencies for the audit step | +| | [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) | Use a tool to check wheels before the end of the run | | **Debugging** | [`debug-keep-container`](https://cibuildwheel.pypa.io/en/stable/options/#debug-keep-container) | Keep the container after running for debugging. | | | [`debug-traceback`](https://cibuildwheel.pypa.io/en/stable/options/#debug-traceback) | Print full traceback when errors occur. | | | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build | - + These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/). diff --git a/docs/options.md b/docs/options.md index 0f2ea2517..69ccc8b99 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1209,6 +1209,8 @@ Platform-specific environment variables are also available:
dependency versions on Linux, use the [`manylinux-*` / `musllinux-*`](#linux-image) options. + There is one exception to this rule - if `audit-requires` specifies `abi3audit`, its version is govenered by this option, because audits take place outside of the build container. + #### Examples !!! tab examples "pyproject.toml" @@ -1695,6 +1697,102 @@ Platform-specific environment variables are also available:
CIBW_TEST_RUNTIME_ANDROID: "args: --managed minVersion" ``` +## Auditing + +### `audit-requires` {: #audit-requires toml env-var } + +> Install Python dependencies for the audit step + +Default: `abi3audit` + +Space-separated list of package dependencies required for the audit command. +These are installed into an isolated environment before running the +[`audit-command`](#audit-command). + +If no audit command is specified, or no audit is required (i.e. your project builds non-abi3 wheels and the command refers only to abi3 wheels), then the audit environment won't be created and this option is ignored. + +If you leave this as the default, the versions of abit3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux. + +#### Examples + +!!! tab examples "pyproject.toml" + + ```toml + # Install twine for wheel metadata checks + [tool.cibuildwheel] + audit-requires = "twine" + + # Install specific versions of audit dependencies + [tool.cibuildwheel] + audit-requires = ["twine==6.1.0", "abi3audit==0.0.17"] + ``` + + In configuration files, you can use an array, and the items will be joined with a space. + +!!! tab examples "Environment variables" + + ```yaml + # Install twine for wheel metadata checks + CIBW_AUDIT_REQUIRES: twine + + # Install specific versions of audit dependencies + CIBW_AUDIT_REQUIRES: twine==6.1.0 abi3audit==0.0.17 + ``` + +### `audit-command` {: #audit-command toml env-var } + +> Use a tool to check wheels before the end of the run + +Default: `abi3audit --strict --report {abi3wheel}` + +Run shell commands to verify your wheels once they are built. Multiple commands can be passed, they should be separated with ` && `. In each command, you must use one of the following placeholders: + +- `{abi3wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path. +- `{wheel}`: inserts the wheel path for all wheels that were built. + +#### Examples + +!!! tab examples "pyproject.toml" + + ```toml + # Run a custom audit tool on all wheels + [tool.cibuildwheel] + audit-command = "my-audit-tool --check {wheel}" + + # Run multiple audit commands + [tool.cibuildwheel] + audit-command = [ + # this command will only run on abi3 wheels + "./my-audit-tool --check-abi3 {abi3wheel}", + # this command will run on all wheels + "./my-audit-tool --check {wheel}", + ] + + # Use twine check to validate wheel metadata + [tool.cibuildwheel] + audit-requires = ["twine"] + audit-command = "twine check {wheel}" + + # Add an additional audit command using overrides, keeping the default + # abi3audit check + inherit.audit-command = "append" + audit-command = "twine check {wheel}" + ``` + +!!! tab examples "Environment variables" + + ```yaml + # Run a custom audit tool on all wheels + CIBW_AUDIT_COMMAND: "my-audit-tool --check {wheel}" + + # Run multiple audit commands + CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3wheel} && ./my-audit-tool --check {wheel}" + + # Use twine check to validate wheel metadata + CIBW_AUDIT_REQUIRES: "twine" + CIBW_AUDIT_COMMAND: "twine check {wheel}" + ``` + ## Debugging From e4c716400d07a344850c7bf17646b9d3637d54ae Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 2 Apr 2026 13:51:19 +0100 Subject: [PATCH 15/48] Move to above testing in docs --- docs/options.md | 193 ++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/docs/options.md b/docs/options.md index 1c4c49dee..58e47de1e 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1308,6 +1308,103 @@ The available Pyodide versions are determined by the version of `pyodide-build` ``` +## Auditing + +### `audit-requires` {: #audit-requires toml env-var } + +> Install Python dependencies for the audit step + +Default: `abi3audit` + +Space-separated list of package dependencies required for the audit command. +These are installed into an isolated environment before running the +[`audit-command`](#audit-command). + +If no audit command is specified, or no audit is required (i.e. your project builds non-abi3 wheels and the command refers only to abi3 wheels), then the audit environment won't be created and this option is ignored. + +If you leave this as the default, the versions of abit3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux. + +#### Examples + +!!! tab examples "pyproject.toml" + + ```toml + # Install twine for wheel metadata checks + [tool.cibuildwheel] + audit-requires = "twine" + + # Install specific versions of audit dependencies + [tool.cibuildwheel] + audit-requires = ["twine==6.1.0", "abi3audit==0.0.17"] + ``` + + In configuration files, you can use an array, and the items will be joined with a space. + +!!! tab examples "Environment variables" + + ```yaml + # Install twine for wheel metadata checks + CIBW_AUDIT_REQUIRES: twine + + # Install specific versions of audit dependencies + CIBW_AUDIT_REQUIRES: twine==6.1.0 abi3audit==0.0.17 + ``` + +### `audit-command` {: #audit-command toml env-var } + +> Use a tool to check wheels before the end of the run + +Default: `abi3audit --strict --report {abi3wheel}` + +Run shell commands to verify your wheels once they are built. Multiple commands can be passed, they should be separated with ` && `. In each command, you must use one of the following placeholders: + +- `{abi3wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path. +- `{wheel}`: inserts the wheel path for all wheels that were built. + +#### Examples + +!!! tab examples "pyproject.toml" + + ```toml + # Run a custom audit tool on all wheels + [tool.cibuildwheel] + audit-command = "my-audit-tool --check {wheel}" + + # Run multiple audit commands + [tool.cibuildwheel] + audit-command = [ + # this command will only run on abi3 wheels + "./my-audit-tool --check-abi3 {abi3wheel}", + # this command will run on all wheels + "./my-audit-tool --check {wheel}", + ] + + # Use twine check to validate wheel metadata + [tool.cibuildwheel] + audit-requires = ["twine"] + audit-command = "twine check {wheel}" + + # Add an additional audit command using overrides, keeping the default + # abi3audit check + inherit.audit-command = "append" + audit-command = "twine check {wheel}" + ``` + +!!! tab examples "Environment variables" + + ```yaml + # Run a custom audit tool on all wheels + CIBW_AUDIT_COMMAND: "my-audit-tool --check {wheel}" + + # Run multiple audit commands + CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3wheel} && ./my-audit-tool --check {wheel}" + + # Use twine check to validate wheel metadata + CIBW_AUDIT_REQUIRES: "twine" + CIBW_AUDIT_COMMAND: "twine check {wheel}" + ``` + + ## Testing ### `test-command` {: #test-command env-var toml} @@ -1700,102 +1797,6 @@ Platform-specific environment variables are also available:
CIBW_TEST_RUNTIME_ANDROID: "args: --managed minVersion" ``` -## Auditing - -### `audit-requires` {: #audit-requires toml env-var } - -> Install Python dependencies for the audit step - -Default: `abi3audit` - -Space-separated list of package dependencies required for the audit command. -These are installed into an isolated environment before running the -[`audit-command`](#audit-command). - -If no audit command is specified, or no audit is required (i.e. your project builds non-abi3 wheels and the command refers only to abi3 wheels), then the audit environment won't be created and this option is ignored. - -If you leave this as the default, the versions of abit3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux. - -#### Examples - -!!! tab examples "pyproject.toml" - - ```toml - # Install twine for wheel metadata checks - [tool.cibuildwheel] - audit-requires = "twine" - - # Install specific versions of audit dependencies - [tool.cibuildwheel] - audit-requires = ["twine==6.1.0", "abi3audit==0.0.17"] - ``` - - In configuration files, you can use an array, and the items will be joined with a space. - -!!! tab examples "Environment variables" - - ```yaml - # Install twine for wheel metadata checks - CIBW_AUDIT_REQUIRES: twine - - # Install specific versions of audit dependencies - CIBW_AUDIT_REQUIRES: twine==6.1.0 abi3audit==0.0.17 - ``` - -### `audit-command` {: #audit-command toml env-var } - -> Use a tool to check wheels before the end of the run - -Default: `abi3audit --strict --report {abi3wheel}` - -Run shell commands to verify your wheels once they are built. Multiple commands can be passed, they should be separated with ` && `. In each command, you must use one of the following placeholders: - -- `{abi3wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path. -- `{wheel}`: inserts the wheel path for all wheels that were built. - -#### Examples - -!!! tab examples "pyproject.toml" - - ```toml - # Run a custom audit tool on all wheels - [tool.cibuildwheel] - audit-command = "my-audit-tool --check {wheel}" - - # Run multiple audit commands - [tool.cibuildwheel] - audit-command = [ - # this command will only run on abi3 wheels - "./my-audit-tool --check-abi3 {abi3wheel}", - # this command will run on all wheels - "./my-audit-tool --check {wheel}", - ] - - # Use twine check to validate wheel metadata - [tool.cibuildwheel] - audit-requires = ["twine"] - audit-command = "twine check {wheel}" - - # Add an additional audit command using overrides, keeping the default - # abi3audit check - inherit.audit-command = "append" - audit-command = "twine check {wheel}" - ``` - -!!! tab examples "Environment variables" - - ```yaml - # Run a custom audit tool on all wheels - CIBW_AUDIT_COMMAND: "my-audit-tool --check {wheel}" - - # Run multiple audit commands - CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3wheel} && ./my-audit-tool --check {wheel}" - - # Use twine check to validate wheel metadata - CIBW_AUDIT_REQUIRES: "twine" - CIBW_AUDIT_COMMAND: "twine check {wheel}" - ``` - ## Debugging From 4f3250d0aec91dc4492ca32c85c8d3c0a1f0e1b5 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 2 Apr 2026 14:08:10 +0100 Subject: [PATCH 16/48] Implement audit-requires and audit-command --- README.md | 6 +- cibuildwheel/__main__.py | 12 -- cibuildwheel/audit.py | 152 ++++++++++------- cibuildwheel/errors.py | 6 + cibuildwheel/options.py | 13 ++ cibuildwheel/platforms/android.py | 2 + cibuildwheel/platforms/ios.py | 7 +- cibuildwheel/platforms/linux.py | 16 +- cibuildwheel/platforms/macos.py | 7 +- cibuildwheel/platforms/pyodide.py | 3 + cibuildwheel/platforms/windows.py | 5 +- cibuildwheel/resources/constraints.in | 1 + cibuildwheel/util/packaging.py | 18 -- cibuildwheel/venv.py | 19 ++- docs/diagram.html | 19 ++- unit_test/abi3audit_test.py | 58 ------- unit_test/audit_test.py | 235 +++++++++++++------------- unit_test/main_tests/conftest.py | 2 +- unit_test/utils_test.py | 22 ++- 19 files changed, 314 insertions(+), 289 deletions(-) delete mode 100644 unit_test/abi3audit_test.py diff --git a/README.md b/README.md index ad91da755..539fcbe7f 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,8 @@ The following diagram summarises the steps that cibuildwheel takes on each platf | | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels | | | [`dependency-versions`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Control the versions of the tools cibuildwheel uses | | | [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) | Specify the Pyodide version to use for `pyodide` platform builds | +| **Auditing** | [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) | Install Python dependencies for the audit step | +| | [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) | Use a tool to check wheels before the end of the run | | **Testing** | [`test-command`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | The command to test each built wheel | | | [`before-test`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel | | | [`test-sources`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Paths that are copied into the working directory of the tests | @@ -165,14 +167,12 @@ The following diagram summarises the steps that cibuildwheel takes on each platf | | [`test-skip`](https://cibuildwheel.pypa.io/en/stable/options/#test-skip) | Skip running tests on some builds | | | [`test-environment`](https://cibuildwheel.pypa.io/en/stable/options/#test-environment) | Set environment variables for the test environment | | | [`test-runtime`](https://cibuildwheel.pypa.io/en/stable/options/#test-runtime) | Controls how the tests will be executed. | -| **Auditing** | [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) | Install Python dependencies for the audit step | -| | [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) | Use a tool to check wheels before the end of the run | | **Debugging** | [`debug-keep-container`](https://cibuildwheel.pypa.io/en/stable/options/#debug-keep-container) | Keep the container after running for debugging. | | | [`debug-traceback`](https://cibuildwheel.pypa.io/en/stable/options/#debug-traceback) | Print full traceback when errors occur. | | | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build | - + These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/). diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 103d351b2..2f6e8d36e 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -17,7 +17,6 @@ import cibuildwheel from cibuildwheel import errors from cibuildwheel.architecture import Architecture, allowed_architectures_check -from cibuildwheel.audit import run_audit from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions from cibuildwheel.logger import log from cibuildwheel.options import CommandLineArguments, Options, compute_options @@ -384,21 +383,10 @@ def build_in_directory(args: CommandLineArguments) -> None: output_dir.mkdir(parents=True, exist_ok=True) - # Snapshot wheels before build to detect newly built ones - wheels_before = {p.name for p in output_dir.glob("*.whl")} - tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True) try: with log.print_summary(options=options): platform_module.build(options, tmp_path) - - # Run audit step after all builds complete - if options.globals.audit_command: - run_audit( - audit_command=options.globals.audit_command, - output_dir=output_dir, - wheels_before=wheels_before, - ) finally: # avoid https://github.com/python/cpython/issues/86962 by performing # cleanup manually diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 65f9cbd25..eeb8f7d97 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -5,85 +5,119 @@ on built wheels after all platform builds are complete. """ -import os import subprocess +import sys from pathlib import Path -from packaging.utils import parse_wheel_filename - -from .logger import log -from .util.helpers import format_safe - - -def is_abi3_wheel(wheel_path: Path) -> bool: - """Check if a wheel is an abi3 wheel by parsing its filename.""" - _, _, _, tags = parse_wheel_filename(wheel_path.name) - return any(t.abi == "abi3" for t in tags) +from cibuildwheel import errors +from cibuildwheel.logger import log +from cibuildwheel.options import BuildOptions +from cibuildwheel.util.cmd import call, shell +from cibuildwheel.util.helpers import prepare_command +from cibuildwheel.util.packaging import is_abi3_wheel +from cibuildwheel.venv import activate_virtualenv, virtualenv def run_audit( - audit_command: str, - output_dir: Path, - wheels_before: set[str], + *, + tmp_dir: Path, + build_options: BuildOptions, + wheel: Path, ) -> None: """ - Run the audit command on wheels built in this run. - - The audit command supports the following placeholders: - - {wheel}: expands to each wheel path, runs the command once per wheel - - {abi3_wheel}: same as {wheel}, but only for abi3 wheels - - If the command contains {abi3_wheel} but no abi3 wheels were produced, - the audit step is skipped. + Run the audit commands on a single wheel. - Args: - audit_command: The command template to run - output_dir: Directory where wheels were output - wheels_before: Set of wheel filenames that existed before the build + Creates a virtualenv (or reuses an existing one) and installs any + audit requirements, then runs each audit command template against + the wheel. Commands containing {abi3_wheel} are skipped for + non-abi3 wheels. """ - if not audit_command: + + if not needs_audit(build_options.audit_command, wheel.name): return - # Find wheels built in this run (new wheels that weren't there before) - all_wheels = sorted(output_dir.glob("*.whl")) - just_built = [w for w in all_wheels if w.name not in wheels_before] + log.step("Auditing wheel...") - if not just_built: - return + audit_venv_dir = tmp_dir / "audit_venv" + if not audit_venv_dir.exists(): + audit_venv_dir.mkdir(parents=True, exist_ok=True) - # Determine if we're auditing abi3 wheels only - abi3_only = "{abi3_wheel}" in audit_command + use_uv = build_options.build_frontend.name in {"uv", "build[uv]"} + dependency_constraint = build_options.dependency_constraints.get_for_python_version( + version=sys.version, tmp_dir=tmp_dir + ) - # Filter wheels if needed - if abi3_only: - wheels_to_audit = [w for w in just_built if is_abi3_wheel(w)] - if not wheels_to_audit: - log.step("Skipping audit step (no abi3 wheels produced)") - return + env = virtualenv( + sys.version, + Path(sys.executable), + audit_venv_dir, + dependency_constraint=dependency_constraint, + use_uv=use_uv, + ) else: - wheels_to_audit = just_built + env = activate_virtualenv(audit_venv_dir) + + # install audit requirements. This is run every time in case the user has + # defined overrides. + audit_requires = build_options.audit_requires + if audit_requires: + print(f"Installing audit dependencies: {', '.join(audit_requires)}") + + pip = ["uv", "pip"] if use_uv else ["pip"] + # we pin if the audit-requires is left as the default "abi3audit" + should_pin = audit_requires == ["abi3audit"] and dependency_constraint + + call( + *pip, + "install", + *(["--constraint", str(dependency_constraint)] if should_pin else []), + *audit_requires, + env=env, + ) - log.step("Running audit...") + audit_command = build_options.audit_command - for wheel in wheels_to_audit: - # Prepare command with placeholders - prepared = format_safe( - audit_command, - wheel=wheel, + for command_template in audit_command: + if "{abi3_wheel}" in command_template and "{wheel}" in command_template: + msg = ( + f"Invalid audit command {command_template!r}: cannot contain both {{abi3_wheel}} " + "and {{wheel}} placeholders" + ) + raise errors.ConfigurationError(msg) + + if "{abi3_wheel}" in command_template and not is_abi3_wheel(wheel.name): + continue + + prepared_command = prepare_command( + command_template, abi3_wheel=wheel, + wheel=wheel, + project=".", + package=build_options.package_dir, ) - log.step(f" Auditing {wheel.name}...") - env = os.environ.copy() - + print(f"Running audit command: {prepared_command}") try: - subprocess.run( - prepared, - shell=True, - check=True, - env=env, - cwd=output_dir, - ) + shell(prepared_command, env=env) except subprocess.CalledProcessError as e: - log.error(f"Audit command failed for {wheel.name}") - raise SystemExit(e.returncode) from e + print(f"Audit command failed with exit code {e.returncode}") + msg = f"Audit command failed: {prepared_command}" + raise errors.AuditCommandFailedError(msg) from e + + +def needs_audit(audit_commands: list[str], wheel_name: str) -> bool: + saw_abi3_placeholder = False + for audit_command in audit_commands: + if "{abi3_wheel}" in audit_command: + saw_abi3_placeholder = True + if is_abi3_wheel(wheel_name): + return True + elif "{wheel}" in audit_command: + return True + + if saw_abi3_placeholder: + print("No audit required for this wheel, as it is not abi3") + else: + print("No audit configured") + + return False diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py index 9f0a71f15..c03b0cd0c 100644 --- a/cibuildwheel/errors.py +++ b/cibuildwheel/errors.py @@ -103,3 +103,9 @@ def __init__(self, wheels: list[str]) -> None: ) super().__init__(message) self.return_code = 8 + + +class AuditCommandFailedError(FatalError): + def __init__(self, message: str) -> None: + super().__init__(message) + self.return_code = 9 diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 5d07b42f8..3148223d5 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -126,6 +126,8 @@ class BuildOptions: test_groups: list[str] test_environment: ParsedEnvironment test_runtime: TestRuntimeConfig + audit_requires: list[str] + audit_command: list[str] build_verbosity: int build_frontend: BuildFrontendConfig config_settings: str @@ -896,6 +898,15 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions: pyodide_version = self.reader.get("pyodide-version", env_plat=False) + audit_command_str = self.reader.get( + "audit-command", option_format=ListFormat(sep=" && ") + ) + audit_command = audit_command_str.split(" && ") if audit_command_str else [] + + audit_requires = self.reader.get( + "audit-requires", option_format=ListFormat(sep=" ") + ).split() + return BuildOptions( globals=self.globals, test_command=test_command, @@ -919,6 +930,8 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions: config_settings=config_settings, container_engine=container_engine, pyodide_version=pyodide_version or None, + audit_command=audit_command, + audit_requires=audit_requires, ) def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None: diff --git a/cibuildwheel/platforms/android.py b/cibuildwheel/platforms/android.py index b4c237ca3..35ea906be 100644 --- a/cibuildwheel/platforms/android.py +++ b/cibuildwheel/platforms/android.py @@ -24,6 +24,7 @@ from cibuildwheel import errors, platforms # pylint: disable=cyclic-import from cibuildwheel.architecture import Architecture, arch_synonym +from cibuildwheel.audit import run_audit from cibuildwheel.frontend import get_build_frontend_extra_flags, parse_config_settings from cibuildwheel.logger import log from cibuildwheel.options import BuildOptions, Options @@ -148,6 +149,7 @@ def build(options: Options, tmp_path: Path) -> None: before_build(state) built_wheel = build_wheel(state) repaired_wheel = repair_wheel(state, built_wheel) + run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name) diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index 9e2474483..54092b065 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -14,6 +14,7 @@ from cibuildwheel import errors from cibuildwheel.architecture import Architecture +from cibuildwheel.audit import run_audit from cibuildwheel.environment import ParsedEnvironment from cibuildwheel.frontend import BuildFrontendName, get_build_frontend_extra_flags from cibuildwheel.logger import log @@ -539,10 +540,12 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) - test_wheel = repaired_wheel - log.step_end() + run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) + + test_wheel = repaired_wheel + if build_options.test_command and build_options.test_selector(config.identifier): if not config.is_simulator: log.step("Skipping tests on non-simulator SDK") diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index caa85c744..4ad371228 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -11,6 +11,7 @@ from cibuildwheel import errors from cibuildwheel.architecture import Architecture +from cibuildwheel.audit import needs_audit, run_audit from cibuildwheel.frontend import get_build_frontend_extra_flags from cibuildwheel.logger import log from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform @@ -19,7 +20,7 @@ from cibuildwheel.util import resources from cibuildwheel.util.file import copy_test_sources from cibuildwheel.util.helpers import prepare_command, unwrap -from cibuildwheel.util.packaging import find_compatible_wheel, is_abi3_wheel, run_abi3audit +from cibuildwheel.util.packaging import find_compatible_wheel if TYPE_CHECKING: from cibuildwheel.typing import PathOrStr @@ -360,14 +361,15 @@ def build_in_container( if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) - if is_abi3_wheel(repaired_wheel.name): - local_abi3audit_dir = local_identifier_tmp_dir / "abi3audit" + log.step_end() + + if needs_audit(build_options.audit_command, repaired_wheel.name): + local_abi3audit_dir = local_identifier_tmp_dir / "audit" local_abi3audit_dir.mkdir(parents=True, exist_ok=True) - container.copy_out(repaired_wheel_dir, local_abi3audit_dir) - local_wheel = local_abi3audit_dir / repaired_wheel.name - run_abi3audit(local_wheel) try: - run_abi3audit(local_wheel) + container.copy_out(repaired_wheel_dir, local_abi3audit_dir) + local_wheel = local_abi3audit_dir / repaired_wheel.name + run_audit(tmp_dir=local_tmp_dir, build_options=build_options, wheel=local_wheel) finally: shutil.rmtree(local_abi3audit_dir, ignore_errors=True) diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 3a41abb02..f47b3a821 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -17,6 +17,7 @@ from cibuildwheel import errors from cibuildwheel.architecture import Architecture +from cibuildwheel.audit import run_audit from cibuildwheel.ci import detect_ci_provider from cibuildwheel.environment import ParsedEnvironment from cibuildwheel.frontend import BuildFrontendName, get_build_frontend_extra_flags @@ -27,7 +28,7 @@ from cibuildwheel.util.cmd import call, shell from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file from cibuildwheel.util.helpers import prepare_command, unwrap -from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version, run_abi3audit +from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version from cibuildwheel.venv import constraint_flags, find_uv, virtualenv @@ -567,10 +568,10 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) - run_abi3audit(repaired_wheel) - log.step_end() + run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) + if build_options.test_command and build_options.test_selector(config.identifier): machine_arch = platform.machine() python_arch = call( diff --git a/cibuildwheel/platforms/pyodide.py b/cibuildwheel/platforms/pyodide.py index 15612a85a..3d64ea788 100644 --- a/cibuildwheel/platforms/pyodide.py +++ b/cibuildwheel/platforms/pyodide.py @@ -16,6 +16,7 @@ from cibuildwheel import errors from cibuildwheel.architecture import Architecture +from cibuildwheel.audit import run_audit from cibuildwheel.environment import ParsedEnvironment from cibuildwheel.frontend import get_build_frontend_extra_flags from cibuildwheel.logger import log @@ -454,6 +455,8 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) + run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) + if build_options.test_command and build_options.test_selector(config.identifier): log.step("Testing wheel...") diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index 5b9c8be5a..fdbd3cf82 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -14,6 +14,7 @@ from cibuildwheel import errors from cibuildwheel.architecture import Architecture +from cibuildwheel.audit import run_audit from cibuildwheel.environment import ParsedEnvironment from cibuildwheel.frontend import BuildFrontendName, get_build_frontend_extra_flags from cibuildwheel.logger import log @@ -29,7 +30,7 @@ move_file, ) from cibuildwheel.util.helpers import prepare_command, unwrap -from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version, run_abi3audit +from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version from cibuildwheel.venv import constraint_flags, find_uv, virtualenv @@ -559,7 +560,7 @@ def build(options: Options, tmp_path: Path) -> None: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) - run_abi3audit(repaired_wheel) + run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) test_selected = options.globals.test_selector(config.identifier) if test_selected and config.arch == "ARM64" != platform_module.machine(): diff --git a/cibuildwheel/resources/constraints.in b/cibuildwheel/resources/constraints.in index 50bfabb6e..1f7841f01 100644 --- a/cibuildwheel/resources/constraints.in +++ b/cibuildwheel/resources/constraints.in @@ -2,3 +2,4 @@ pip build delocate virtualenv +abi3audit diff --git a/cibuildwheel/util/packaging.py b/cibuildwheel/util/packaging.py index bab5df2c8..1dc71eaf0 100644 --- a/cibuildwheel/util/packaging.py +++ b/cibuildwheel/util/packaging.py @@ -1,6 +1,4 @@ import shlex -import subprocess -import sys from collections.abc import Mapping, Sequence from dataclasses import dataclass, field from pathlib import Path, PurePath @@ -8,7 +6,6 @@ from packaging.utils import parse_wheel_filename -from cibuildwheel.logger import log from cibuildwheel.util import resources from cibuildwheel.util.cmd import call from cibuildwheel.util.helpers import parse_key_value_string, unwrap @@ -186,18 +183,3 @@ def is_abi3_wheel(wheel_name: str) -> bool: """Check if a wheel uses the abi3 stable ABI based on its filename.""" _, _, _, tags = parse_wheel_filename(wheel_name) return any(tag.abi == "abi3" for tag in tags) - - -def run_abi3audit(wheel_path: Path) -> None: - """Run abi3audit on the given wheel if it is an abi3 wheel. - - Raises subprocess.CalledProcessError if abi3audit reports violations. - """ - if not is_abi3_wheel(wheel_path.name): - return - - log.step("Running abi3audit...") - subprocess.run( - [sys.executable, "-m", "abi3audit", "--strict", "--report", str(wheel_path)], - check=True, - ) diff --git a/cibuildwheel/venv.py b/cibuildwheel/venv.py index dd4fe90aa..5e68da9b8 100644 --- a/cibuildwheel/venv.py +++ b/cibuildwheel/venv.py @@ -139,10 +139,7 @@ def virtualenv( python, venv_path, ) - paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")] - venv_env = os.environ.copy() if env is None else env.copy() - venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]]) - venv_env["VIRTUAL_ENV"] = str(venv_path) + venv_env = activate_virtualenv(venv_path, env=env) if not use_uv and pip_version == "embed": call( "python", @@ -158,6 +155,20 @@ def virtualenv( return venv_env +def activate_virtualenv( + venv_path: Path, + env: dict[str, str] | None = None, +) -> dict[str, str]: + """ + Return a copy of the environment with the virtualenv at `venv_path` activated. + """ + paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")] + venv_env = os.environ.copy() if env is None else env.copy() + venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]]) + venv_env["VIRTUAL_ENV"] = str(venv_path) + return venv_env + + def find_uv() -> Path | None: # Prefer uv in our environment with contextlib.suppress(ImportError, FileNotFoundError): diff --git a/docs/diagram.html b/docs/diagram.html index 52c70b9e7..9567dd19f 100644 --- a/docs/diagram.html +++ b/docs/diagram.html @@ -29,7 +29,7 @@
If tests are configured
@@ -167,6 +167,21 @@ }, }, ], + [ + { + env: "CIBW_AUDIT_COMMAND", + href: 'options/#audit-command', + label: 'audit wheel', + platforms: ['linux', 'macos', 'windows'], + style: 'dot', + // optional: true, + tooltip: { + title: 'CIBW_AUDIT_COMMAND', + tag: 'Optional step', + description: 'Runs a shell command to check each built wheel. By default this runs abi3audit if produced wheels are abi3.' + }, + } + ], [ { href: 'options/#before-test', @@ -420,7 +435,7 @@ grid-column: 3 / -2; } .grid-outline.testVenv { - grid-column: 9 / span 3; + grid-column: 10 / span 3; } .grid-outline .outline { position: absolute; diff --git a/unit_test/abi3audit_test.py b/unit_test/abi3audit_test.py deleted file mode 100644 index b03c98c37..000000000 --- a/unit_test/abi3audit_test.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -import subprocess -import sys -from pathlib import Path -from unittest.mock import patch - -import pytest - -from cibuildwheel.util.packaging import is_abi3_wheel, run_abi3audit - - -class TestIsAbi3Wheel: - def test_abi3_wheel(self): - assert is_abi3_wheel("foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") is True - - def test_abi3_wheel_macos(self): - assert is_abi3_wheel("foo-1.0-cp311-abi3-macosx_11_0_arm64.whl") is True - - def test_abi3_wheel_windows(self): - assert is_abi3_wheel("foo-1.0-cp310-abi3-win_amd64.whl") is True - - def test_cpython_wheel(self): - assert is_abi3_wheel("foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") is False - - def test_none_any_wheel(self): - assert is_abi3_wheel("foo-1.0-py3-none-any.whl") is False - - def test_none_platform_wheel(self): - assert is_abi3_wheel("foo-1.0-cp310-none-win_amd64.whl") is False - - -class TestRunAbi3audit: - def test_skips_non_abi3_wheel(self): - wheel = Path("/tmp/foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") - with patch("cibuildwheel.util.packaging.subprocess.run") as mock_run: - run_abi3audit(wheel) - mock_run.assert_not_called() - - def test_runs_on_abi3_wheel(self): - wheel = Path("/tmp/foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") - with patch("cibuildwheel.util.packaging.subprocess.run") as mock_run: - run_abi3audit(wheel) - mock_run.assert_called_once_with( - [sys.executable, "-m", "abi3audit", "--strict", "--report", str(wheel)], - check=True, - ) - - def test_raises_on_failure(self): - wheel = Path("/tmp/foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") - with ( - patch( - "cibuildwheel.util.packaging.subprocess.run", - side_effect=subprocess.CalledProcessError(1, "abi3audit"), - ), - pytest.raises(subprocess.CalledProcessError), - ): - run_abi3audit(wheel) diff --git a/unit_test/audit_test.py b/unit_test/audit_test.py index bf1ef5c67..4287ddb3c 100644 --- a/unit_test/audit_test.py +++ b/unit_test/audit_test.py @@ -1,134 +1,135 @@ +import contextlib +import subprocess from pathlib import Path +from unittest.mock import Mock, patch import pytest -from cibuildwheel.audit import is_abi3_wheel, run_audit +from cibuildwheel import errors +from cibuildwheel.audit import needs_audit, run_audit -class TestIsAbi3Wheel: - def test_abi3_wheel(self) -> None: - assert is_abi3_wheel(Path("example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl")) +def mock_virtualenv() -> contextlib.AbstractContextManager[Mock]: + return patch( + "cibuildwheel.audit.virtualenv", + return_value={ + "PATH": "/bin", + "VIRTUAL_ENV": "/tmp/v", + }, + ) - def test_abi3_wheel_macos(self) -> None: - assert is_abi3_wheel(Path("example-1.0.0-cp39-abi3-macosx_10_9_x86_64.whl")) - def test_abi3_wheel_windows(self) -> None: - assert is_abi3_wheel(Path("example-1.0.0-cp310-abi3-win_amd64.whl")) +class TestNeedsAudit: + def test_empty_commands(self) -> None: + assert needs_audit([], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") is False - def test_non_abi3_wheel(self) -> None: - assert not is_abi3_wheel(Path("example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")) - - def test_pure_python_wheel(self) -> None: - assert not is_abi3_wheel(Path("example-1.0.0-py3-none-any.whl")) - - -class TestRunAudit: - def test_empty_command_does_nothing(self, tmp_path: Path) -> None: - # Create a wheel file - wheel_path = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Should not raise and should be a no-op - run_audit( - audit_command="", - output_dir=tmp_path, - wheels_before=set(), - ) - - def test_audit_runs_on_new_wheels(self, tmp_path: Path) -> None: - # Create a wheel file (simulating a build) - wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Create a marker file to verify command ran - marker = tmp_path / "audit_ran.txt" - - run_audit( - audit_command=f"touch {marker}", - output_dir=tmp_path, - wheels_before=set(), - ) - - assert marker.exists() - - def test_audit_skips_old_wheels(self, tmp_path: Path) -> None: - # Create a wheel file - wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Create a marker file to verify command ran - marker = tmp_path / "audit_ran.txt" - - # Pre-existing wheel should be skipped - run_audit( - audit_command=f"touch {marker}", - output_dir=tmp_path, - wheels_before={wheel_path.name}, - ) - - assert not marker.exists() - - def test_abi3_only_mode_skips_non_abi3(self, tmp_path: Path) -> None: - # Create a non-abi3 wheel - wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Create a marker file to verify command ran - marker = tmp_path / "audit_ran.txt" - - run_audit( - audit_command=f"echo {{abi3_wheel}} && touch {marker}", - output_dir=tmp_path, - wheels_before=set(), + def test_wheel_placeholder_matches_any_wheel(self) -> None: + assert needs_audit( + ["my-tool {wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" ) - # Should not run because no abi3 wheels - assert not marker.exists() - - def test_abi3_only_mode_runs_on_abi3(self, tmp_path: Path) -> None: - # Create an abi3 wheel - wheel_path = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Create a marker file to verify command ran - marker = tmp_path / "audit_ran.txt" - - run_audit( - audit_command=f"echo {{abi3_wheel}} && touch {marker}", - output_dir=tmp_path, - wheels_before=set(), + def test_abi3_placeholder_skips_non_abi3(self) -> None: + assert ( + needs_audit( + ["abi3audit {abi3_wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + ) + is False ) - assert marker.exists() - - def test_wheel_placeholder_expanded(self, tmp_path: Path) -> None: - # Create a wheel file - wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" - wheel_path.touch() - - # Write wheel path to a file to verify expansion - output_file = tmp_path / "wheel_path.txt" - - run_audit( - audit_command=f"echo {{wheel}} > {output_file}", - output_dir=tmp_path, - wheels_before=set(), + def test_abi3_placeholder_matches_abi3(self) -> None: + assert needs_audit( + ["abi3audit {abi3_wheel}"], "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" ) - assert output_file.exists() - content = output_file.read_text().strip() - assert content == str(wheel_path) + def test_mixed_commands_matches_if_any_applies(self) -> None: + commands = ["abi3audit {abi3_wheel}", "twine check {wheel}"] + # non-abi3 wheel still needs audit because of the {wheel} command + assert needs_audit(commands, "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") - def test_audit_fails_on_error(self, tmp_path: Path) -> None: - # Create a wheel file - wheel_path = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" - wheel_path.touch() - with pytest.raises(SystemExit) as exc_info: - run_audit( - audit_command="exit 1", - output_dir=tmp_path, - wheels_before=set(), - ) - - assert exc_info.value.code == 1 +class TestRunAudit: + @pytest.fixture + def mock_build_options(self) -> Mock: + opts = Mock() + opts.audit_command = [] + opts.audit_requires = [] + opts.package_dir = Path("/fake/package") + opts.build_frontend.name = "build" + opts.dependency_constraints.get_for_python_version.return_value = None + return opts + + def test_no_commands_does_nothing(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = [] + + with patch("cibuildwheel.audit.shell") as mock_shell: + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + mock_shell.assert_not_called() + + def test_runs_wheel_command(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["my-tool {wheel}"] + + with mock_virtualenv(), patch("cibuildwheel.audit.shell") as mock_shell: + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + mock_shell.assert_called_once() + cmd = mock_shell.call_args[0][0] + assert str(wheel) in cmd + + def test_abi3_command_skipped_for_non_abi3( + self, tmp_path: Path, mock_build_options: Mock + ) -> None: + wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["abi3audit {abi3_wheel}"] + + with patch("cibuildwheel.audit.shell") as mock_shell: + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + mock_shell.assert_not_called() + + def test_abi3_command_runs_for_abi3(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["abi3audit {abi3_wheel}"] + + with ( + mock_virtualenv(), + patch("cibuildwheel.audit.shell") as mock_shell, + ): + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + mock_shell.assert_called_once() + cmd = mock_shell.call_args[0][0] + assert str(wheel) in cmd + + def test_raises_on_command_failure(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["failing-tool {wheel}"] + + with ( + mock_virtualenv(), + patch( + "cibuildwheel.audit.shell", + side_effect=subprocess.CalledProcessError(1, "failing-tool"), + ), + pytest.raises(errors.AuditCommandFailedError), + ): + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + + def test_multiple_commands_all_run(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["tool-a {wheel}", "tool-b {wheel}"] + + with ( + mock_virtualenv(), + patch("cibuildwheel.audit.shell") as mock_shell, + ): + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) + assert mock_shell.call_count == 2 + + def test_both_placeholders_raises(self, tmp_path: Path, mock_build_options: Mock) -> None: + wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl" + mock_build_options.audit_command = ["my-tool {wheel} {abi3_wheel}"] + + with ( + mock_virtualenv(), + pytest.raises(errors.ConfigurationError, match="cannot contain both"), + ): + run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel) diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index df683c67d..5aa4397bd 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -69,7 +69,7 @@ def allow_empty(monkeypatch: pytest.MonkeyPatch, fake_package_dir: list[str]) -> monkeypatch.setattr(sys, "argv", [*fake_package_dir, "--allow-empty"]) -@pytest.fixture(params=["linux", "macos", "windows"]) +@pytest.fixture(params=["linux", "macos", "windows", "pyodide"]) def platform(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) -> str: platform_value: str = request.param monkeypatch.setenv("CIBW_PLATFORM", platform_value) diff --git a/unit_test/utils_test.py b/unit_test/utils_test.py index 3ce67e273..7a58ad498 100644 --- a/unit_test/utils_test.py +++ b/unit_test/utils_test.py @@ -15,7 +15,7 @@ unwrap, unwrap_preserving_paragraphs, ) -from cibuildwheel.util.packaging import find_compatible_wheel +from cibuildwheel.util.packaging import find_compatible_wheel, is_abi3_wheel def test_format_safe() -> None: @@ -401,3 +401,23 @@ def test_unwrap_preserving_paragraphs() -> None: """) == "paragraph one\n\nparagraph two" ) + + +class TestIsAbi3Wheel: + def test_abi3_wheel(self) -> None: + assert is_abi3_wheel("foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") is True + + def test_abi3_wheel_macos(self) -> None: + assert is_abi3_wheel("foo-1.0-cp311-abi3-macosx_11_0_arm64.whl") is True + + def test_abi3_wheel_windows(self) -> None: + assert is_abi3_wheel("foo-1.0-cp310-abi3-win_amd64.whl") is True + + def test_cpython_wheel(self) -> None: + assert is_abi3_wheel("foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") is False + + def test_none_any_wheel(self) -> None: + assert is_abi3_wheel("foo-1.0-py3-none-any.whl") is False + + def test_none_platform_wheel(self) -> None: + assert is_abi3_wheel("foo-1.0-cp310-none-win_amd64.whl") is False From 46ce64a8d376090c7608d8c0c32bfb21d49eb769 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 2 Apr 2026 14:22:10 +0100 Subject: [PATCH 17/48] Some cleanups after self-review --- bin/generate_schema.py | 8 +++ cibuildwheel/audit.py | 7 -- .../resources/cibuildwheel.schema.json | 67 ++++++++++++++++++- pyproject.toml | 1 - unit_test/main_tests/conftest.py | 2 +- unit_test/options_toml_test.py | 24 ++++--- 6 files changed, 89 insertions(+), 20 deletions(-) diff --git a/bin/generate_schema.py b/bin/generate_schema.py index b2f631185..72eed1512 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -39,6 +39,12 @@ description: cibuildwheel's settings. type: object properties: + audit-command: + description: Execute a shell command to audit each wheel after all builds complete. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels. + type: string_array + audit-requires: + description: Install Python dependencies for the audit step. + type: string_array archs: description: Change the architectures built on your machine by default. type: string_array @@ -309,6 +315,8 @@ type: object additionalProperties: false properties: + audit-command: {"$ref": "#/$defs/inherit"} + audit-requires: {"$ref": "#/$defs/inherit"} before-all: {"$ref": "#/$defs/inherit"} before-build: {"$ref": "#/$defs/inherit"} xbuild-tools: {"$ref": "#/$defs/inherit"} diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index eeb8f7d97..48e1f1579 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -1,10 +1,3 @@ -""" -Audit step for wheels built by cibuildwheel. - -This module provides functionality to run audit commands (like abi3audit) -on built wheels after all platform builds are complete. -""" - import subprocess import sys from pathlib import Path diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index 735e5e006..bf42e557c 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -27,7 +27,7 @@ "description": "cibuildwheel's settings.", "type": "object", "properties": { - "audit": { + "audit-command": { "description": "Execute a shell command to audit each wheel after all builds complete. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.", "oneOf": [ { @@ -40,7 +40,22 @@ } } ], - "title": "CIBW_AUDIT" + "title": "CIBW_AUDIT_COMMAND" + }, + "audit-requires": { + "description": "Install Python dependencies for the audit step.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "title": "CIBW_AUDIT_REQUIRES" }, "archs": { "description": "Change the architectures built on your machine by default.", @@ -650,6 +665,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/$defs/inherit" + }, + "audit-requires": { + "$ref": "#/$defs/inherit" + }, "before-all": { "$ref": "#/$defs/inherit" }, @@ -697,6 +718,12 @@ } } }, + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "before-all": { "$ref": "#/properties/before-all" }, @@ -815,6 +842,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, @@ -945,6 +978,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, @@ -1008,6 +1047,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, @@ -1084,6 +1129,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, @@ -1147,6 +1198,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, @@ -1210,6 +1267,12 @@ "type": "object", "additionalProperties": false, "properties": { + "audit-command": { + "$ref": "#/properties/audit-command" + }, + "audit-requires": { + "$ref": "#/properties/audit-requires" + }, "archs": { "$ref": "#/properties/archs" }, diff --git a/pyproject.toml b/pyproject.toml index 6dbe5cf7a..dbac2cdc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ classifiers = [ "Topic :: Software Development :: Build Tools", ] dependencies = [ - "abi3audit", "bashlex!=0.13", "bracex", "build>=1.0.0", diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index 5aa4397bd..df683c67d 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -69,7 +69,7 @@ def allow_empty(monkeypatch: pytest.MonkeyPatch, fake_package_dir: list[str]) -> monkeypatch.setattr(sys, "argv", [*fake_package_dir, "--allow-empty"]) -@pytest.fixture(params=["linux", "macos", "windows", "pyodide"]) +@pytest.fixture(params=["linux", "macos", "windows"]) def platform(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) -> str: platform_value: str = request.param monkeypatch.setenv("CIBW_PLATFORM", platform_value) diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index c3de3b9b7..aca4ba4df 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -543,36 +543,39 @@ def test_overrides_inherit(tmp_path: Path) -> None: ) -def test_audit_option(tmp_path: Path, platform: PlatformName) -> None: +def test_audit_command_option(tmp_path: Path, platform: PlatformName) -> None: pyproject_toml: Path = tmp_path / "pyproject.toml" pyproject_toml.write_text( """ [tool.cibuildwheel] -audit = "abi3audit {abi3_wheel}" +audit-command = "abi3audit {abi3_wheel}" """ ) options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) - assert options_reader.get("audit", option_format=ListFormat(" && ")) == "abi3audit {abi3_wheel}" + assert ( + options_reader.get("audit-command", option_format=ListFormat(" && ")) + == "abi3audit {abi3_wheel}" + ) -def test_audit_option_list(tmp_path: Path, platform: PlatformName) -> None: +def test_audit_command_option_list(tmp_path: Path, platform: PlatformName) -> None: pyproject_toml: Path = tmp_path / "pyproject.toml" pyproject_toml.write_text( """ [tool.cibuildwheel] -audit = ["first command", "second command"] +audit-command = ["first command", "second command"] """ ) options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) assert ( - options_reader.get("audit", option_format=ListFormat(" && ")) + options_reader.get("audit-command", option_format=ListFormat(" && ")) == "first command && second command" ) -def test_audit_option_env(tmp_path: Path, platform: PlatformName) -> None: +def test_audit_command_option_env(tmp_path: Path, platform: PlatformName) -> None: pyproject_toml: Path = tmp_path / "pyproject.toml" pyproject_toml.write_text( """ @@ -581,6 +584,9 @@ def test_audit_option_env(tmp_path: Path, platform: PlatformName) -> None: ) options_reader = OptionsReader( - pyproject_toml, platform=platform, env={"CIBW_AUDIT": "my-audit-tool {wheel}"} + pyproject_toml, platform=platform, env={"CIBW_AUDIT_COMMAND": "my-audit-tool {wheel}"} + ) + assert ( + options_reader.get("audit-command", option_format=ListFormat(" && ")) + == "my-audit-tool {wheel}" ) - assert options_reader.get("audit", option_format=ListFormat(" && ")) == "my-audit-tool {wheel}" From 678d8f5e329b3f957342d0c5b4980254039f2f6c Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 2 Apr 2026 14:25:34 +0100 Subject: [PATCH 18/48] Add default value --- cibuildwheel/resources/defaults.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index a58952d8d..86d714b22 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -5,7 +5,8 @@ test-skip = "" enable = [] archs = ["auto"] -audit = "" +audit-requires = ["abi3audit"] +audit-command = "abi3audit --strict --report {abi3wheel}" build-frontend = "default" config-settings = {} dependency-versions = "pinned" From 08330a4c9ff131918ffbba26d0fa22d166acdbdc Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 2 Apr 2026 14:28:18 +0100 Subject: [PATCH 19/48] fix type errors --- test/test_abi3audit.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index f4c35553f..0a4cecc17 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -1,5 +1,6 @@ import subprocess import textwrap +from pathlib import Path import pytest @@ -71,7 +72,7 @@ @utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") -def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): +def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: """Test that abi3audit runs automatically on abi3 wheels.""" project_dir = tmp_path / "project" limited_api_project.generate(project_dir) @@ -92,7 +93,9 @@ def test_abi3audit_runs_on_abi3_wheel(tmp_path, capfd): @utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") -def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): +def test_abi3audit_skipped_for_non_abi3_wheel( + tmp_path: Path, capfd: pytest.CaptureFixture[str] +) -> None: """Test that abi3audit does not run for non-abi3 wheels.""" project_dir = tmp_path / "project" basic_project = test_projects.new_c_project() @@ -113,7 +116,7 @@ def test_abi3audit_skipped_for_non_abi3_wheel(tmp_path, capfd): @utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") -def test_abi3audit_detects_violation(tmp_path, capfd): +def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: """Test that abi3audit catches stable ABI violations and fails the build. This project tags the wheel as cp310-abi3 but uses PyUnicode_AsUTF8, From d145df60674a414931842415883252473f70aec3 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:55:09 +0530 Subject: [PATCH 20/48] the key is `audit-command`, not `audit` --- cibuildwheel/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 3148223d5..b626a042a 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -698,7 +698,7 @@ def globals(self) -> GlobalOptions: ) test_selector = TestSelector(skip_config=test_skip) - audit_command = self.reader.get("audit", option_format=ListFormat(sep=" && ")) + audit_command = self.reader.get("audit-command", option_format=ListFormat(sep=" && ")) return GlobalOptions( package_dir=package_dir, From ba25da06bc85ea72fdd9577d6cb3eeb2d1fdcfd1 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:04:11 +0530 Subject: [PATCH 21/48] Add a variety of tests for audit requires options --- unit_test/options_toml_test.py | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index aca4ba4df..0689be824 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -590,3 +590,113 @@ def test_audit_command_option_env(tmp_path: Path, platform: PlatformName) -> Non options_reader.get("audit-command", option_format=ListFormat(" && ")) == "my-audit-tool {wheel}" ) + + +def test_audit_requires_option(tmp_path: Path, platform: PlatformName) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit-requires = "abi3audit" +""" + ) + + options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) + assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit" + + +def test_audit_requires_option_list(tmp_path: Path, platform: PlatformName) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit-requires = ["abi3audit", "twine"] +""" + ) + + options_reader = OptionsReader(pyproject_toml, platform=platform, env={}) + assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit twine" + + +def test_audit_requires_option_env(tmp_path: Path, platform: PlatformName) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +""" + ) + + options_reader = OptionsReader( + pyproject_toml, platform=platform, env={"CIBW_AUDIT_REQUIRES": "custom-audit-tool"} + ) + assert ( + options_reader.get("audit-requires", option_format=ListFormat(" ")) == "custom-audit-tool" + ) + + +def test_audit_requires_option_env_override(tmp_path: Path, platform: PlatformName) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit-requires = "abi3audit" +""" + ) + + options_reader = OptionsReader( + pyproject_toml, platform=platform, env={"CIBW_AUDIT_REQUIRES": "custom-audit-tool"} + ) + assert ( + options_reader.get("audit-requires", option_format=ListFormat(" ")) == "custom-audit-tool" + ) + + +def test_audit_requires_platform_specific(tmp_path: Path) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit-requires = "abi3audit" + +[tool.cibuildwheel.linux] +audit-requires = ["abi3audit", "check-wheel-contents"] # whatever + +[tool.cibuildwheel.macos] +audit-requires = ["check-wheel-contents", "pydistcheck"] # whatever +""" + ) + + linux_reader = OptionsReader(pyproject_toml, platform="linux", env={}) + assert ( + linux_reader.get("audit-requires", option_format=ListFormat(" ")) + == "abi3audit check-wheel-contents" + ) + + macos_reader = OptionsReader(pyproject_toml, platform="macos", env={}) + assert ( + macos_reader.get("audit-requires", option_format=ListFormat(" ")) + == "check-wheel-contents pydistcheck" + ) + + windows_reader = OptionsReader(pyproject_toml, platform="windows", env={}) + assert windows_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit" + + +def test_audit_requires_platform_env_override(tmp_path: Path) -> None: + pyproject_toml: Path = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +audit-requires = "abi3audit" +""" + ) + + options_reader = OptionsReader( + pyproject_toml, + platform="linux", + env={ + "CIBW_AUDIT_REQUIRES": "some-fallback-tool", + "CIBW_AUDIT_REQUIRES_LINUX": "linux-audit-tool", + }, + ) + assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "linux-audit-tool" From 80b65299fa00395209df2f6db9f85a7d2fe33f79 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:05:08 +0530 Subject: [PATCH 22/48] Add `test_audit_requires` similar to `test_test_requires` --- unit_test/main_tests/main_options_test.py | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index 5e9e008aa..abf1b6c84 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -297,6 +297,30 @@ def test_test_requires( assert build_options.test_requires == (test_requires or "").split() +@pytest.mark.parametrize("audit_requires", [None, "abi3audit", "abi3audit custom-audit-tool"]) +@pytest.mark.parametrize("platform_specific", [False, True]) +def test_audit_requires( + audit_requires: str | None, + platform_specific: bool, + platform: str, + intercepted_build_args: "ArgsInterceptor", + monkeypatch: pytest.MonkeyPatch, +) -> None: + if audit_requires is not None: + if platform_specific: + monkeypatch.setenv("CIBW_AUDIT_REQUIRES_" + platform.upper(), audit_requires) + monkeypatch.setenv("CIBW_AUDIT_REQUIRES", "overwritten") + else: + monkeypatch.setenv("CIBW_AUDIT_REQUIRES", audit_requires) + + main() + + build_options = intercepted_build_args.args[0].build_options(identifier=None) + + expected = (audit_requires or "abi3audit").split() + assert build_options.audit_requires == expected + + @pytest.mark.parametrize("test_extras", [None, "extras"]) @pytest.mark.parametrize("platform_specific", [False, True]) def test_test_extras( From d65a33a6eb7e529a338276aa284bd9a494c1f028 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:24:26 +0530 Subject: [PATCH 23/48] Add some configurability-related audit tests --- test/test_abi3audit.py | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index 0a4cecc17..a4fa6bdfd 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -136,3 +136,81 @@ def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixtur captured = capfd.readouterr() assert "Running abi3audit" in captured.out + + +def test_custom_audit_command(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: + project_dir = tmp_path / "project" + test_projects.new_c_project().generate(project_dir) + + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_AUDIT_COMMAND": "echo custom-audit-ok {wheel}", + "CIBW_AUDIT_REQUIRES": "", + "CIBW_ARCHS": "native", + }, + single_python=True, + ) + + assert len(actual_wheels) >= 1 + captured = capfd.readouterr() + assert "Auditing wheel" in captured.out + assert "custom-audit-ok" in captured.out + + +def test_custom_audit_requires(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: + project_dir = tmp_path / "project" + test_projects.new_c_project().generate(project_dir) + + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_AUDIT_REQUIRES": "pycowsay", + "CIBW_AUDIT_COMMAND": ( + "python -c \"import pycowsay; print(pycowsay.cow('moo'))\" {wheel}" + ), + "CIBW_ARCHS": "native", + }, + single_python=True, + ) + + assert len(actual_wheels) >= 1 + captured = capfd.readouterr() + assert "Installing audit dependencies: pycowsay" in captured.out + assert "moo" in captured.out + + +def test_empty_audit_command_disables_audit( + tmp_path: Path, capfd: pytest.CaptureFixture[str] +) -> None: + project_dir = tmp_path / "project" + test_projects.new_c_project().generate(project_dir) + + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_AUDIT_COMMAND": "", + "CIBW_ARCHS": "native", + }, + single_python=True, + ) + + assert len(actual_wheels) >= 1 + captured = capfd.readouterr() + assert "Auditing wheel" not in captured.out + + +def test_custom_audit_command_failure(tmp_path: Path) -> None: + project_dir = tmp_path / "project" + test_projects.new_c_project().generate(project_dir) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_AUDIT_COMMAND": 'python -c "import sys; sys.exit(1)" {wheel}', + "CIBW_AUDIT_REQUIRES": "", + "CIBW_ARCHS": "native", + }, + single_python=True, + ) From c53eb6b51278ed2c83f5193b0e2e8a015586294b Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:27:51 +0530 Subject: [PATCH 24/48] Fix parsing error with options docs leaving out commands --- docs/options.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/options.md b/docs/options.md index 58e47de1e..9e40d8dfd 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1370,12 +1370,10 @@ Run shell commands to verify your wheels once they are built. Multiple commands [tool.cibuildwheel] audit-command = "my-audit-tool --check {wheel}" - # Run multiple audit commands + # Run multiple audit commands, one for abi3 wheels only and one for all wheels [tool.cibuildwheel] audit-command = [ - # this command will only run on abi3 wheels "./my-audit-tool --check-abi3 {abi3wheel}", - # this command will run on all wheels "./my-audit-tool --check {wheel}", ] @@ -1384,8 +1382,9 @@ Run shell commands to verify your wheels once they are built. Multiple commands audit-requires = ["twine"] audit-command = "twine check {wheel}" - # Add an additional audit command using overrides, keeping the default - # abi3audit check + # Add an additional audit command using overrides, keeping the default abi3audit check + [[tool.cibuildwheel.overrides]] + select = "*" inherit.audit-command = "append" audit-command = "twine check {wheel}" ``` From 6c20ebc258109c67134c47816dc172f81b2a8a95 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:47:30 +0530 Subject: [PATCH 25/48] Better way to extract version (maybe helps Pyodide?) --- cibuildwheel/audit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 48e1f1579..4c6bcb221 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -36,12 +36,13 @@ def run_audit( audit_venv_dir.mkdir(parents=True, exist_ok=True) use_uv = build_options.build_frontend.name in {"uv", "build[uv]"} + version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" dependency_constraint = build_options.dependency_constraints.get_for_python_version( - version=sys.version, tmp_dir=tmp_dir + version=version, tmp_dir=tmp_dir ) env = virtualenv( - sys.version, + version, Path(sys.executable), audit_venv_dir, dependency_constraint=dependency_constraint, From 7b1d6883794b902023185ba802fe8822ac8a5245 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:50:41 +0530 Subject: [PATCH 26/48] Fix a case of unbound `use_uv` --- cibuildwheel/audit.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 4c6bcb221..ff334eb1f 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -31,16 +31,16 @@ def run_audit( log.step("Auditing wheel...") + use_uv = build_options.build_frontend.name in {"uv", "build[uv]"} + version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + dependency_constraint = build_options.dependency_constraints.get_for_python_version( + version=version, tmp_dir=tmp_dir + ) + audit_venv_dir = tmp_dir / "audit_venv" if not audit_venv_dir.exists(): audit_venv_dir.mkdir(parents=True, exist_ok=True) - use_uv = build_options.build_frontend.name in {"uv", "build[uv]"} - version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" - dependency_constraint = build_options.dependency_constraints.get_for_python_version( - version=version, tmp_dir=tmp_dir - ) - env = virtualenv( version, Path(sys.executable), From b95caa63316691eeb55b8d565bcdd077e18c0a9c Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:51:00 +0530 Subject: [PATCH 27/48] Standardise: rename to `abi3_wheel` --- cibuildwheel/resources/defaults.toml | 2 +- docs/options.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 86d714b22..0713cddb5 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -6,7 +6,7 @@ enable = [] archs = ["auto"] audit-requires = ["abi3audit"] -audit-command = "abi3audit --strict --report {abi3wheel}" +audit-command = "abi3audit --strict --report {abi3_wheel}" build-frontend = "default" config-settings = {} dependency-versions = "pinned" diff --git a/docs/options.md b/docs/options.md index 9e40d8dfd..1af64f6de 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1354,11 +1354,11 @@ If you leave this as the default, the versions of abit3audit and libraries are p > Use a tool to check wheels before the end of the run -Default: `abi3audit --strict --report {abi3wheel}` +Default: `abi3audit --strict --report {abi3_wheel}` Run shell commands to verify your wheels once they are built. Multiple commands can be passed, they should be separated with ` && `. In each command, you must use one of the following placeholders: -- `{abi3wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path. +- `{abi3_wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path. - `{wheel}`: inserts the wheel path for all wheels that were built. #### Examples @@ -1373,7 +1373,7 @@ Run shell commands to verify your wheels once they are built. Multiple commands # Run multiple audit commands, one for abi3 wheels only and one for all wheels [tool.cibuildwheel] audit-command = [ - "./my-audit-tool --check-abi3 {abi3wheel}", + "./my-audit-tool --check-abi3 {abi3_wheel}", "./my-audit-tool --check {wheel}", ] @@ -1396,7 +1396,7 @@ Run shell commands to verify your wheels once they are built. Multiple commands CIBW_AUDIT_COMMAND: "my-audit-tool --check {wheel}" # Run multiple audit commands - CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3wheel} && ./my-audit-tool --check {wheel}" + CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3_wheel} && ./my-audit-tool --check {wheel}" # Use twine check to validate wheel metadata CIBW_AUDIT_REQUIRES: "twine" From 7886e962397715a38146da3312e26078ad7c1b6f Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:57:11 +0530 Subject: [PATCH 28/48] Fix audit command run message --- test/test_abi3audit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index a4fa6bdfd..6b4744973 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -89,7 +89,7 @@ def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixtu assert len(actual_wheels) >= 1 captured = capfd.readouterr() - assert "Running abi3audit" in captured.out + assert "Running audit command: abi3audit" in captured.out @utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") @@ -112,7 +112,7 @@ def test_abi3audit_skipped_for_non_abi3_wheel( assert len(actual_wheels) >= 1 captured = capfd.readouterr() - assert "Running abi3audit" not in captured.out + assert "Running audit command: abi3audit" not in captured.out @utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") @@ -135,7 +135,7 @@ def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixtur ) captured = capfd.readouterr() - assert "Running abi3audit" in captured.out + assert "Running audit command: abi3audit" in captured.out def test_custom_audit_command(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: From f7d9ccb50a5bc52508dc62f9e867a33b4a227b3e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:08:29 +0530 Subject: [PATCH 29/48] Simplify custom audit command a bit --- test/test_abi3audit.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index 6b4744973..c89190c7e 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -166,9 +166,7 @@ def test_custom_audit_requires(tmp_path: Path, capfd: pytest.CaptureFixture[str] project_dir, add_env={ "CIBW_AUDIT_REQUIRES": "pycowsay", - "CIBW_AUDIT_COMMAND": ( - "python -c \"import pycowsay; print(pycowsay.cow('moo'))\" {wheel}" - ), + "CIBW_AUDIT_COMMAND": "pycowsay moo {wheel}", "CIBW_ARCHS": "native", }, single_python=True, From 672299bb418fbd46130962c156700f1daef2d4f6 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:27:09 +0530 Subject: [PATCH 30/48] Remove unnecessary skip for Pyodide --- test/test_abi3audit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index c89190c7e..6aab47f00 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -92,7 +92,6 @@ def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixtu assert "Running audit command: abi3audit" in captured.out -@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") def test_abi3audit_skipped_for_non_abi3_wheel( tmp_path: Path, capfd: pytest.CaptureFixture[str] ) -> None: From b791437dad1510a4a160c2d6709a679fd372863e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:29:26 +0530 Subject: [PATCH 31/48] Pyodide should have no default audit command --- cibuildwheel/resources/defaults.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 0713cddb5..ab7d31905 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -66,3 +66,4 @@ repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest [tool.cibuildwheel.ios] [tool.cibuildwheel.pyodide] +audit-command = "" From dc354e0ff21540817945fcfe6c9a7bbb2458835a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:29:49 +0530 Subject: [PATCH 32/48] More accurate skip messages for Pyodide skips --- test/test_abi3audit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index 6aab47f00..7ed1990df 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -71,7 +71,7 @@ violating_abi3_project.files["pyproject.toml"] = pyproject_toml -@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") +@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)") def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: """Test that abi3audit runs automatically on abi3 wheels.""" project_dir = tmp_path / "project" @@ -114,7 +114,7 @@ def test_abi3audit_skipped_for_non_abi3_wheel( assert "Running audit command: abi3audit" not in captured.out -@utils.skip_if_pyodide("Pyodide doesn't build abi3 wheels, so abi3audit is not relevant") +@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)") def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None: """Test that abi3audit catches stable ABI violations and fails the build. From e74561275c24a533189dbe60f712c1024347fa59 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:50:17 +0530 Subject: [PATCH 33/48] Wheels are audited after they are repaired --- bin/generate_schema.py | 2 +- cibuildwheel/resources/cibuildwheel.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/generate_schema.py b/bin/generate_schema.py index 72eed1512..5cc1e0a40 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -40,7 +40,7 @@ type: object properties: audit-command: - description: Execute a shell command to audit each wheel after all builds complete. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels. + description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels. type: string_array audit-requires: description: Install Python dependencies for the audit step. diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index bf42e557c..022faf129 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -28,7 +28,7 @@ "type": "object", "properties": { "audit-command": { - "description": "Execute a shell command to audit each wheel after all builds complete. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.", + "description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.", "oneOf": [ { "type": "string" From ab87881e06ac87fa1dc1c30ec3e9a4ed2cd73c5f Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:51:47 +0530 Subject: [PATCH 34/48] Regenerate constraints to include `abi3audit` --- .../resources/constraints-python310.txt | 58 +++++++++++++++++-- .../resources/constraints-python311.txt | 58 +++++++++++++++++-- .../resources/constraints-python312.txt | 58 +++++++++++++++++-- .../resources/constraints-python313.txt | 58 +++++++++++++++++-- .../resources/constraints-python314.txt | 58 +++++++++++++++++-- .../resources/constraints-python38.txt | 57 +++++++++++++++++- .../resources/constraints-python39.txt | 58 +++++++++++++++++-- cibuildwheel/resources/constraints.txt | 58 +++++++++++++++++-- 8 files changed, 427 insertions(+), 36 deletions(-) diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index a01a3addb..e800e9595 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -1,42 +1,92 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv +exceptiongroup==1.3.1 + # via cattrs filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize importlib-metadata==9.0.0 # via build +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit tomli==2.4.1 # via build typing-extensions==4.15.0 # via + # cattrs # delocate + # exceptiongroup # virtualenv -virtualenv==21.2.0 +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in zipp==3.23.0 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index cb5e1ebe1..7228e7ccb 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -1,9 +1,23 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 @@ -12,23 +26,57 @@ filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit typing-extensions==4.15.0 - # via delocate -virtualenv==21.2.0 + # via + # cattrs + # delocate +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python312.txt b/cibuildwheel/resources/constraints-python312.txt index cb5e1ebe1..7228e7ccb 100644 --- a/cibuildwheel/resources/constraints-python312.txt +++ b/cibuildwheel/resources/constraints-python312.txt @@ -1,9 +1,23 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 @@ -12,23 +26,57 @@ filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit typing-extensions==4.15.0 - # via delocate -virtualenv==21.2.0 + # via + # cattrs + # delocate +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python313.txt b/cibuildwheel/resources/constraints-python313.txt index cb5e1ebe1..7228e7ccb 100644 --- a/cibuildwheel/resources/constraints-python313.txt +++ b/cibuildwheel/resources/constraints-python313.txt @@ -1,9 +1,23 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 @@ -12,23 +26,57 @@ filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit typing-extensions==4.15.0 - # via delocate -virtualenv==21.2.0 + # via + # cattrs + # delocate +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python314.txt b/cibuildwheel/resources/constraints-python314.txt index cb5e1ebe1..7228e7ccb 100644 --- a/cibuildwheel/resources/constraints-python314.txt +++ b/cibuildwheel/resources/constraints-python314.txt @@ -1,9 +1,23 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 @@ -12,23 +26,57 @@ filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit typing-extensions==4.15.0 - # via delocate -virtualenv==21.2.0 + # via + # cattrs + # delocate +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 5b2c13ad3..220ed0aae 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -1,42 +1,93 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.17 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.4.29 + # via abi3audit altgraph==0.17.5 # via macholib +attrs==25.3.0 + # via + # cattrs + # requests-cache build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in +cattrs==24.1.3 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv +exceptiongroup==1.3.1 + # via cattrs filelock==3.16.1 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize importlib-metadata==8.5.0 # via build +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate -packaging==26.0 +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +packaging==24.2 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==25.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.3.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.19.2 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.32.4 + # via + # abi3audit + # requests-cache +requests-cache==1.2.1 + # via abi3audit +rich==13.8.1 + # via abi3audit tomli==2.4.1 # via build typing-extensions==4.13.2 # via + # cattrs # delocate + # exceptiongroup + # rich # virtualenv -virtualenv==21.2.0 +url-normalize==2.2.1 + # via requests-cache +urllib3==2.2.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in zipp==3.20.2 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 66d2377fb..9c7042a8b 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -1,42 +1,92 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.25 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.4.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==25.3.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv +exceptiongroup==1.3.1 + # via cattrs filelock==3.19.1 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize importlib-metadata==8.7.1 # via build +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate -packaging==26.0 +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +packaging==25.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.4.0 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.32.5 + # via + # abi3audit + # requests-cache +requests-cache==1.2.1 + # via abi3audit +rich==14.2.0 + # via abi3audit tomli==2.4.1 # via build typing-extensions==4.15.0 # via + # cattrs # delocate + # exceptiongroup # virtualenv -virtualenv==21.2.0 +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in zipp==3.23.0 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index cb5e1ebe1..7228e7ccb 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -1,9 +1,23 @@ # This file was autogenerated by uv via the following command: # nox -s update_constraints +abi3audit==0.0.26 + # via -r cibuildwheel/resources/constraints.in +abi3info==2025.11.29 + # via abi3audit altgraph==0.17.5 # via macholib -build==1.4.2 +attrs==26.1.0 + # via + # cattrs + # requests-cache +build==1.4.3 # via -r cibuildwheel/resources/constraints.in +cattrs==26.1.0 + # via requests-cache +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 @@ -12,23 +26,57 @@ filelock==3.25.2 # via # python-discovery # virtualenv +idna==3.11 + # via + # requests + # url-normalize +kaitaistruct==0.11 + # via abi3audit macholib==1.16.4 # via delocate +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py packaging==26.0 # via + # abi3audit # build # delocate +pefile==2024.8.26 + # via abi3audit pip==26.0.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # python-discovery + # requests-cache # virtualenv +pyelftools==0.32 + # via abi3audit +pygments==2.20.0 + # via rich pyproject-hooks==1.2.0 # via build -python-discovery==1.2.1 +python-discovery==1.2.2 # via virtualenv +requests==2.33.1 + # via + # abi3audit + # requests-cache +requests-cache==1.3.1 + # via abi3audit +rich==15.0.0 + # via abi3audit typing-extensions==4.15.0 - # via delocate -virtualenv==21.2.0 + # via + # cattrs + # delocate +url-normalize==2.2.1 + # via requests-cache +urllib3==2.6.3 + # via + # requests + # requests-cache +virtualenv==21.2.1 # via -r cibuildwheel/resources/constraints.in From 389c580794ae29eb521b59e1998a2af252482dd2 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:52:09 +0530 Subject: [PATCH 35/48] Fix typos --- docs/options.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/options.md b/docs/options.md index 1af64f6de..653e29e48 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1212,7 +1212,7 @@ Platform-specific environment variables are also available:
dependency versions on Linux, use the [`manylinux-*` / `musllinux-*`](#linux-image) options. - There is one exception to this rule - if `audit-requires` specifies `abi3audit`, its version is govenered by this option, because audits take place outside of the build container. + There is one exception to this rule - if `audit-requires` specifies `abi3audit`, its version is governed by this option, because audits take place outside of the build container. #### Examples @@ -1322,7 +1322,7 @@ These are installed into an isolated environment before running the If no audit command is specified, or no audit is required (i.e. your project builds non-abi3 wheels and the command refers only to abi3 wheels), then the audit environment won't be created and this option is ignored. -If you leave this as the default, the versions of abit3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux. +If you leave this as the default, the versions of abi3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux. #### Examples From d80ed3b5a4e0f29f33348bae0021f596007fb7a2 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:30:00 +0530 Subject: [PATCH 36/48] Some attempts for Windows fixes --- cibuildwheel/audit.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index ff334eb1f..64f835a83 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -8,7 +8,7 @@ from cibuildwheel.util.cmd import call, shell from cibuildwheel.util.helpers import prepare_command from cibuildwheel.util.packaging import is_abi3_wheel -from cibuildwheel.venv import activate_virtualenv, virtualenv +from cibuildwheel.venv import activate_virtualenv, find_uv, virtualenv def run_audit( @@ -31,7 +31,7 @@ def run_audit( log.step("Auditing wheel...") - use_uv = build_options.build_frontend.name in {"uv", "build[uv]"} + use_uv = build_options.build_frontend.name in {"build[uv]", "uv"} version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" dependency_constraint = build_options.dependency_constraints.get_for_python_version( version=version, tmp_dir=tmp_dir @@ -57,7 +57,13 @@ def run_audit( if audit_requires: print(f"Installing audit dependencies: {', '.join(audit_requires)}") - pip = ["uv", "pip"] if use_uv else ["pip"] + pip: list[str] + if use_uv: + uv_path = find_uv() + assert uv_path is not None + pip = [str(uv_path), "pip"] + else: + pip = ["pip"] # we pin if the audit-requires is left as the default "abi3audit" should_pin = audit_requires == ["abi3audit"] and dependency_constraint From 5bcd54ca6e92e06c998579256afc0bf2ce706a9a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:32:48 +0530 Subject: [PATCH 37/48] Check `pyvenv.cfg` instead of directory existence --- cibuildwheel/audit.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 64f835a83..8bdb7dbf5 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -38,9 +38,7 @@ def run_audit( ) audit_venv_dir = tmp_dir / "audit_venv" - if not audit_venv_dir.exists(): - audit_venv_dir.mkdir(parents=True, exist_ok=True) - + if not (audit_venv_dir / "pyvenv.cfg").exists(): env = virtualenv( version, Path(sys.executable), From 71683657bb8f07cf97397b92aee65493c25ea36b Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:38:36 +0530 Subject: [PATCH 38/48] Add validation for lack of wheel placeholders --- cibuildwheel/audit.py | 7 +++++++ unit_test/audit_test.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 8bdb7dbf5..920a2a1c0 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -106,6 +106,13 @@ def run_audit( def needs_audit(audit_commands: list[str], wheel_name: str) -> bool: saw_abi3_placeholder = False for audit_command in audit_commands: + if "{abi3_wheel}" not in audit_command and "{wheel}" not in audit_command: + msg = ( + f"Invalid audit command {audit_command!r}: must contain either " + "{{abi3_wheel}} or {{wheel}} placeholder" + ) + raise errors.ConfigurationError(msg) + if "{abi3_wheel}" in audit_command: saw_abi3_placeholder = True if is_abi3_wheel(wheel_name): diff --git a/unit_test/audit_test.py b/unit_test/audit_test.py index 4287ddb3c..49495d0ce 100644 --- a/unit_test/audit_test.py +++ b/unit_test/audit_test.py @@ -46,6 +46,10 @@ def test_mixed_commands_matches_if_any_applies(self) -> None: # non-abi3 wheel still needs audit because of the {wheel} command assert needs_audit(commands, "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") + def test_command_without_placeholder_raises(self) -> None: + with pytest.raises(errors.ConfigurationError, match="must contain either"): + needs_audit(["my-tool"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") + class TestRunAudit: @pytest.fixture From 45e8825e0bf2309b6fcba9f6edf68e8a56fbd05c Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:01:30 +0530 Subject: [PATCH 39/48] Try yet another Windows `uv` fix --- cibuildwheel/audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 920a2a1c0..5ba121b4c 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -31,7 +31,7 @@ def run_audit( log.step("Auditing wheel...") - use_uv = build_options.build_frontend.name in {"build[uv]", "uv"} + use_uv = find_uv() is not None version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" dependency_constraint = build_options.dependency_constraints.get_for_python_version( version=version, tmp_dir=tmp_dir From 6bdb9d707925172546b3be07f8b448b91c7e6938 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:48:58 +0530 Subject: [PATCH 40/48] Regenerate diagram and re-trigger Azure CI --- docs/data/how-it-works.png | Bin 93464 -> 98369 bytes docs/diagram.html | 1 - 2 files changed, 1 deletion(-) diff --git a/docs/data/how-it-works.png b/docs/data/how-it-works.png index bbbc2a301be45486cd350eb2496956ef500e55b0..e066fcdd758e027cdbcc7e62c0cd02460fa18371 100644 GIT binary patch literal 98369 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

(PLML(_0m=ZnjMaF+#2Wax0q27PXkI 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 >!MqSDwn=Ydc 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 Date: Mon, 13 Apr 2026 18:08:31 +0530 Subject: [PATCH 41/48] Add missing `import sys` for abi3 C extension tests --- test/test_abi3audit.py | 2 ++ test/test_abi_variants.py | 1 + 2 files changed, 3 insertions(+) diff --git a/test/test_abi3audit.py b/test/test_abi3audit.py index 7ed1990df..adacf25c5 100644 --- a/test/test_abi3audit.py +++ b/test/test_abi3audit.py @@ -15,6 +15,7 @@ limited_api_project = test_projects.new_c_project( setup_py_add=textwrap.dedent( r""" + import sys import sysconfig IS_CPYTHON = sys.implementation.name == "cpython" @@ -40,6 +41,7 @@ violating_abi3_project = test_projects.new_c_project( setup_py_add=textwrap.dedent( r""" + import sys import sysconfig IS_CPYTHON = sys.implementation.name == "cpython" diff --git a/test/test_abi_variants.py b/test/test_abi_variants.py index 25fc9ca5d..b8f180ce0 100644 --- a/test/test_abi_variants.py +++ b/test/test_abi_variants.py @@ -14,6 +14,7 @@ limited_api_project = test_projects.new_c_project( setup_py_add=textwrap.dedent( r""" + import sys import sysconfig IS_CPYTHON = sys.implementation.name == "cpython" From 854ee13bd601b153951d622e1f015104d3b5dd5e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:13:32 +0530 Subject: [PATCH 42/48] Remove audit-command at the global level --- cibuildwheel/options.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 5e2808e6f..ea57b51ce 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -90,7 +90,6 @@ class GlobalOptions: test_selector: TestSelector architectures: set[Architecture] allow_empty: bool - audit_command: str @dataclasses.dataclass(frozen=True) @@ -700,8 +699,6 @@ def globals(self) -> GlobalOptions: ) test_selector = TestSelector(skip_config=test_skip) - audit_command = self.reader.get("audit-command", option_format=ListFormat(sep=" && ")) - return GlobalOptions( package_dir=package_dir, output_dir=output_dir, @@ -709,7 +706,6 @@ def globals(self) -> GlobalOptions: test_selector=test_selector, architectures=architectures, allow_empty=allow_empty, - audit_command=audit_command, ) def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None: From e3c3b01d164303f78b916f1c9b1d874b63647567 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:16:13 +0530 Subject: [PATCH 43/48] Clarify `abi3audit` pinning a little bit --- docs/options.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/options.md b/docs/options.md index 653e29e48..eef50791e 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1212,7 +1212,9 @@ Platform-specific environment variables are also available:
dependency versions on Linux, use the [`manylinux-*` / `musllinux-*`](#linux-image) options. - There is one exception to this rule - if `audit-requires` specifies `abi3audit`, its version is governed by this option, because audits take place outside of the build container. + There is one exception to this rule - when `audit-requires` is left as the + default `["abi3audit"]`, the `abi3audit` version is governed by this option, + because audits take place outside of the build container. #### Examples From dcb4cbb2003469323e3c665ecc3fc7ef323deef5 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:35:53 +0530 Subject: [PATCH 44/48] Regen constraints --- .../resources/constraints-pyodide312.txt | 28 +++++++++---------- .../resources/constraints-pyodide313.txt | 28 +++++++++---------- .../resources/constraints-python310.txt | 8 +++--- .../resources/constraints-python311.txt | 6 ++-- .../resources/constraints-python312.txt | 6 ++-- .../resources/constraints-python313.txt | 6 ++-- .../resources/constraints-python314.txt | 6 ++-- .../resources/constraints-python38.txt | 2 +- .../resources/constraints-python39.txt | 4 +-- cibuildwheel/resources/constraints.txt | 6 ++-- 10 files changed, 50 insertions(+), 50 deletions(-) diff --git a/cibuildwheel/resources/constraints-pyodide312.txt b/cibuildwheel/resources/constraints-pyodide312.txt index 693f4602e..510263869 100644 --- a/cibuildwheel/resources/constraints-pyodide312.txt +++ b/cibuildwheel/resources/constraints-pyodide312.txt @@ -2,15 +2,15 @@ # nox -s update_constraints annotated-types==0.7.0 # via pydantic -auditwheel-emscripten==0.2.3 +auditwheel-emscripten==0.2.4 # via pyodide-build -build==1.2.2.post1 +build==1.4.3 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # pyodide-build certifi==2026.2.25 # via requests -charset-normalizer==3.4.6 +charset-normalizer==3.4.7 # via requests click==8.1.8 # via @@ -19,7 +19,7 @@ click==8.1.8 # pyodide-cli distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -31,7 +31,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # auditwheel-emscripten # build @@ -39,20 +39,20 @@ packaging==26.0 # wheel pip==26.0.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # pyodide-build # python-discovery # virtualenv -pydantic==2.12.5 +pydantic==2.13.0 # via # pyodide-build # pyodide-lock -pydantic-core==2.41.5 +pydantic-core==2.46.0 # via pydantic -pygments==2.19.2 +pygments==2.20.0 # via rich -pyodide-build==0.33.0 +pyodide-build==0.34.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in pyodide-cli==0.5.0 # via @@ -62,11 +62,11 @@ pyodide-lock==0.1.2 # via pyodide-build pyproject-hooks==1.2.0 # via build -python-discovery==1.2.0 +python-discovery==1.2.2 # via virtualenv -requests==2.32.5 +requests==2.33.1 # via pyodide-build -rich==14.3.3 +rich==15.0.0 # via # pyodide-build # pyodide-cli @@ -81,7 +81,7 @@ typing-inspection==0.4.2 # via pydantic urllib3==2.6.3 # via requests -virtualenv==21.2.0 +virtualenv==21.2.4 # via # build # pyodide-build diff --git a/cibuildwheel/resources/constraints-pyodide313.txt b/cibuildwheel/resources/constraints-pyodide313.txt index 693f4602e..510263869 100644 --- a/cibuildwheel/resources/constraints-pyodide313.txt +++ b/cibuildwheel/resources/constraints-pyodide313.txt @@ -2,15 +2,15 @@ # nox -s update_constraints annotated-types==0.7.0 # via pydantic -auditwheel-emscripten==0.2.3 +auditwheel-emscripten==0.2.4 # via pyodide-build -build==1.2.2.post1 +build==1.4.3 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # pyodide-build certifi==2026.2.25 # via requests -charset-normalizer==3.4.6 +charset-normalizer==3.4.7 # via requests click==8.1.8 # via @@ -19,7 +19,7 @@ click==8.1.8 # pyodide-cli distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -31,7 +31,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # auditwheel-emscripten # build @@ -39,20 +39,20 @@ packaging==26.0 # wheel pip==26.0.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -platformdirs==4.9.4 +platformdirs==4.9.6 # via # pyodide-build # python-discovery # virtualenv -pydantic==2.12.5 +pydantic==2.13.0 # via # pyodide-build # pyodide-lock -pydantic-core==2.41.5 +pydantic-core==2.46.0 # via pydantic -pygments==2.19.2 +pygments==2.20.0 # via rich -pyodide-build==0.33.0 +pyodide-build==0.34.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in pyodide-cli==0.5.0 # via @@ -62,11 +62,11 @@ pyodide-lock==0.1.2 # via pyodide-build pyproject-hooks==1.2.0 # via build -python-discovery==1.2.0 +python-discovery==1.2.2 # via virtualenv -requests==2.32.5 +requests==2.33.1 # via pyodide-build -rich==14.3.3 +rich==15.0.0 # via # pyodide-build # pyodide-cli @@ -81,7 +81,7 @@ typing-inspection==0.4.2 # via pydantic urllib3==2.6.3 # via requests -virtualenv==21.2.0 +virtualenv==21.2.4 # via # build # pyodide-build diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index e800e9595..1c6c3fe05 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -24,7 +24,7 @@ distlib==0.4.0 # via virtualenv exceptiongroup==1.3.1 # via cattrs -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -42,7 +42,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -86,7 +86,7 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in -zipp==3.23.0 +zipp==3.23.1 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index 7228e7ccb..f9b23d07b 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -22,7 +22,7 @@ delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -38,7 +38,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -78,5 +78,5 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python312.txt b/cibuildwheel/resources/constraints-python312.txt index 7228e7ccb..f9b23d07b 100644 --- a/cibuildwheel/resources/constraints-python312.txt +++ b/cibuildwheel/resources/constraints-python312.txt @@ -22,7 +22,7 @@ delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -38,7 +38,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -78,5 +78,5 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python313.txt b/cibuildwheel/resources/constraints-python313.txt index 7228e7ccb..f9b23d07b 100644 --- a/cibuildwheel/resources/constraints-python313.txt +++ b/cibuildwheel/resources/constraints-python313.txt @@ -22,7 +22,7 @@ delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -38,7 +38,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -78,5 +78,5 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python314.txt b/cibuildwheel/resources/constraints-python314.txt index 7228e7ccb..f9b23d07b 100644 --- a/cibuildwheel/resources/constraints-python314.txt +++ b/cibuildwheel/resources/constraints-python314.txt @@ -22,7 +22,7 @@ delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -38,7 +38,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -78,5 +78,5 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 220ed0aae..bd18367f9 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -87,7 +87,7 @@ urllib3==2.2.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in zipp==3.20.2 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 9c7042a8b..0838442a5 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -86,7 +86,7 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in -zipp==3.23.0 +zipp==3.23.1 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index 7228e7ccb..f9b23d07b 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -22,7 +22,7 @@ delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv -filelock==3.25.2 +filelock==3.28.0 # via # python-discovery # virtualenv @@ -38,7 +38,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.0 +packaging==26.1 # via # abi3audit # build @@ -78,5 +78,5 @@ urllib3==2.6.3 # via # requests # requests-cache -virtualenv==21.2.1 +virtualenv==21.2.4 # via -r cibuildwheel/resources/constraints.in From cb81bd4023caa7c4c6fabfb5bdf380f3f88898bd Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:43:18 +0530 Subject: [PATCH 45/48] Discard changes to cibuildwheel/resources/constraints-pyodide312.txt --- .../resources/constraints-pyodide312.txt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cibuildwheel/resources/constraints-pyodide312.txt b/cibuildwheel/resources/constraints-pyodide312.txt index 510263869..693f4602e 100644 --- a/cibuildwheel/resources/constraints-pyodide312.txt +++ b/cibuildwheel/resources/constraints-pyodide312.txt @@ -2,15 +2,15 @@ # nox -s update_constraints annotated-types==0.7.0 # via pydantic -auditwheel-emscripten==0.2.4 +auditwheel-emscripten==0.2.3 # via pyodide-build -build==1.4.3 +build==1.2.2.post1 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # pyodide-build certifi==2026.2.25 # via requests -charset-normalizer==3.4.7 +charset-normalizer==3.4.6 # via requests click==8.1.8 # via @@ -19,7 +19,7 @@ click==8.1.8 # pyodide-cli distlib==0.4.0 # via virtualenv -filelock==3.28.0 +filelock==3.25.2 # via # python-discovery # virtualenv @@ -31,7 +31,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.1 +packaging==26.0 # via # auditwheel-emscripten # build @@ -39,20 +39,20 @@ packaging==26.1 # wheel pip==26.0.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -platformdirs==4.9.6 +platformdirs==4.9.4 # via # pyodide-build # python-discovery # virtualenv -pydantic==2.13.0 +pydantic==2.12.5 # via # pyodide-build # pyodide-lock -pydantic-core==2.46.0 +pydantic-core==2.41.5 # via pydantic -pygments==2.20.0 +pygments==2.19.2 # via rich -pyodide-build==0.34.1 +pyodide-build==0.33.0 # via -r .nox/update_constraints/tmp/constraints-pyodide.in pyodide-cli==0.5.0 # via @@ -62,11 +62,11 @@ pyodide-lock==0.1.2 # via pyodide-build pyproject-hooks==1.2.0 # via build -python-discovery==1.2.2 +python-discovery==1.2.0 # via virtualenv -requests==2.33.1 +requests==2.32.5 # via pyodide-build -rich==15.0.0 +rich==14.3.3 # via # pyodide-build # pyodide-cli @@ -81,7 +81,7 @@ typing-inspection==0.4.2 # via pydantic urllib3==2.6.3 # via requests -virtualenv==21.2.4 +virtualenv==21.2.0 # via # build # pyodide-build From 7cfec024134c37631230e6275f41ac6327b1efbc Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:43:30 +0530 Subject: [PATCH 46/48] Discard changes to cibuildwheel/resources/constraints-pyodide313.txt --- .../resources/constraints-pyodide313.txt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cibuildwheel/resources/constraints-pyodide313.txt b/cibuildwheel/resources/constraints-pyodide313.txt index 510263869..693f4602e 100644 --- a/cibuildwheel/resources/constraints-pyodide313.txt +++ b/cibuildwheel/resources/constraints-pyodide313.txt @@ -2,15 +2,15 @@ # nox -s update_constraints annotated-types==0.7.0 # via pydantic -auditwheel-emscripten==0.2.4 +auditwheel-emscripten==0.2.3 # via pyodide-build -build==1.4.3 +build==1.2.2.post1 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # pyodide-build certifi==2026.2.25 # via requests -charset-normalizer==3.4.7 +charset-normalizer==3.4.6 # via requests click==8.1.8 # via @@ -19,7 +19,7 @@ click==8.1.8 # pyodide-cli distlib==0.4.0 # via virtualenv -filelock==3.28.0 +filelock==3.25.2 # via # python-discovery # virtualenv @@ -31,7 +31,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==26.1 +packaging==26.0 # via # auditwheel-emscripten # build @@ -39,20 +39,20 @@ packaging==26.1 # wheel pip==26.0.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -platformdirs==4.9.6 +platformdirs==4.9.4 # via # pyodide-build # python-discovery # virtualenv -pydantic==2.13.0 +pydantic==2.12.5 # via # pyodide-build # pyodide-lock -pydantic-core==2.46.0 +pydantic-core==2.41.5 # via pydantic -pygments==2.20.0 +pygments==2.19.2 # via rich -pyodide-build==0.34.1 +pyodide-build==0.33.0 # via -r .nox/update_constraints/tmp/constraints-pyodide.in pyodide-cli==0.5.0 # via @@ -62,11 +62,11 @@ pyodide-lock==0.1.2 # via pyodide-build pyproject-hooks==1.2.0 # via build -python-discovery==1.2.2 +python-discovery==1.2.0 # via virtualenv -requests==2.33.1 +requests==2.32.5 # via pyodide-build -rich==15.0.0 +rich==14.3.3 # via # pyodide-build # pyodide-cli @@ -81,7 +81,7 @@ typing-inspection==0.4.2 # via pydantic urllib3==2.6.3 # via requests -virtualenv==21.2.4 +virtualenv==21.2.0 # via # build # pyodide-build From 9395b993b66604812aa7077d04d4f8a6a3031323 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 24 Apr 2026 16:40:18 +0100 Subject: [PATCH 47/48] try opt-in uv again --- cibuildwheel/audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 5ba121b4c..920a2a1c0 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -31,7 +31,7 @@ def run_audit( log.step("Auditing wheel...") - use_uv = find_uv() is not None + use_uv = build_options.build_frontend.name in {"build[uv]", "uv"} version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" dependency_constraint = build_options.dependency_constraints.get_for_python_version( version=version, tmp_dir=tmp_dir From 3e7c6f733ca68acd8b0b23509a6c6ffbdbf827da Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 24 Apr 2026 17:18:12 +0100 Subject: [PATCH 48/48] fix issue on windows on Python 3.13 related to nested venvs On win / python 3.13, virtualenv creates a venv where the 'home' points back to the venv that sys.executable was running in, rather than the root install. that seemingly leads to problems with package resolution, where pip.exe couldn't find the pip python package. this appears to fix it! --- cibuildwheel/audit.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/audit.py b/cibuildwheel/audit.py index 920a2a1c0..f91b01558 100644 --- a/cibuildwheel/audit.py +++ b/cibuildwheel/audit.py @@ -37,11 +37,15 @@ def run_audit( version=version, tmp_dir=tmp_dir ) + # Use the base interpreter, not the venv python, to avoid nested-venv + # issues where pip can't be found (seen on Windows + Python 3.13). + host_python = Path(getattr(sys, "_base_executable", sys.executable)) + audit_venv_dir = tmp_dir / "audit_venv" if not (audit_venv_dir / "pyvenv.cfg").exists(): env = virtualenv( version, - Path(sys.executable), + host_python, audit_venv_dir, dependency_constraint=dependency_constraint, use_uv=use_uv,