From ef18186c455b3d4225b9de89e623cb2a77c6091c Mon Sep 17 00:00:00 2001 From: mayeut Date: Sun, 19 Jun 2022 18:03:05 +0200 Subject: [PATCH] fix: error out if multiple wheels with the same name are produced We shouldn't silently overwrite wheels that are produced in the same run that have the same filename, as this implies a misconfiguration. --- cibuildwheel/linux.py | 5 +++++ cibuildwheel/macos.py | 4 ++++ cibuildwheel/util.py | 14 ++++++++++++++ cibuildwheel/windows.py | 4 ++++ test/test_same_wheel.py | 42 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 test/test_same_wheel.py diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 58fe4dc9..d6b39e74 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -10,6 +10,7 @@ from .logger import log from .options import Options from .typing import OrderedDict, PathOrStr, assert_never from .util import ( + AlreadyBuiltWheelError, BuildSelector, NonPlatformWheelError, find_compatible_wheel, @@ -255,6 +256,10 @@ def build_on_docker( repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl") + for repaired_wheel in repaired_wheels: + if repaired_wheel.name in {wheel.name for wheel in built_wheels}: + raise AlreadyBuiltWheelError(repaired_wheel.name) + if build_options.test_command and build_options.test_selector(config.identifier): log.step("Testing wheel...") diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 56942cfb..d5fff5ed 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -17,6 +17,7 @@ from .options import Options from .typing import Literal, PathOrStr, assert_never from .util import ( CIBW_CACHE_PATH, + AlreadyBuiltWheelError, BuildFrontend, BuildSelector, NonPlatformWheelError, @@ -408,6 +409,9 @@ def build(options: Options, tmp_path: Path) -> None: repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + if repaired_wheel.name in {wheel.name for wheel in built_wheels}: + raise AlreadyBuiltWheelError(repaired_wheel.name) + log.step_end() if build_options.test_command and build_options.test_selector(config.identifier): diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 45cd243a..268e7ce4 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -373,6 +373,20 @@ class NonPlatformWheelError(Exception): super().__init__(message) +class AlreadyBuiltWheelError(Exception): + def __init__(self, wheel_name: str) -> None: + message = textwrap.dedent( + f""" + cibuildwheel: Build failed because a wheel named {wheel_name} was already generated in the current run. + + If you expected another wheel to be generated, check your project configuration, or run + cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs. + """ + ) + + super().__init__(message) + + def strtobool(val: str) -> bool: return val.lower() in {"y", "yes", "t", "true", "on", "1"} diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 5f0d2344..b4ae6e07 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -17,6 +17,7 @@ from .options import Options from .typing import PathOrStr, assert_never from .util import ( CIBW_CACHE_PATH, + AlreadyBuiltWheelError, BuildFrontend, BuildSelector, NonPlatformWheelError, @@ -365,6 +366,9 @@ def build(options: Options, tmp_path: Path) -> None: repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + if repaired_wheel.name in {wheel.name for wheel in built_wheels}: + raise AlreadyBuiltWheelError(repaired_wheel.name) + if build_options.test_command and options.globals.test_selector(config.identifier): log.step("Testing wheel...") # set up a virtual environment to install and test from, to make sure diff --git a/test/test_same_wheel.py b/test/test_same_wheel.py new file mode 100644 index 00000000..d89f28cb --- /dev/null +++ b/test/test_same_wheel.py @@ -0,0 +1,42 @@ +import subprocess +from test import test_projects + +import pytest + +from . import utils + +basic_project = test_projects.new_c_project() +basic_project.files[ + "repair.py" +] = """ +import shutil +import sys +from pathlib import Path + +wheel = Path(sys.argv[1]) +dest_dir = Path(sys.argv[2]) +platform = wheel.stem.split("-")[-1] +name = f"spam-0.1.0-py2-none-{platform}.whl" +dest = dest_dir / name +dest_dir.mkdir(parents=True, exist_ok=True) +if dest.exists(): + dest.unlink() +shutil.copy(wheel, dest) +""" + + +def test(tmp_path, capfd): + # this test checks that a generated wheel name shall be unique in a given cibuildwheel run + project_dir = tmp_path / "project" + basic_project.generate(project_dir) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_REPAIR_WHEEL_COMMAND": "python repair.py {wheel} {dest_dir}", + }, + ) + + captured = capfd.readouterr() + assert "Build failed because a wheel named" in captured.err