diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py
index 6f828b63..28939719 100644
--- a/cibuildwheel/errors.py
+++ b/cibuildwheel/errors.py
@@ -67,3 +67,21 @@ class OCIEngineTooOldError(FatalError):
def __init__(self, message: str) -> None:
super().__init__(message)
self.return_code = 7
+
+
+class RepairStepProducedNoWheelError(FatalError):
+ def __init__(self) -> None:
+ message = textwrap.dedent(
+ """
+ Build failed because the repair step completed successfully but
+ did not produce a wheel.
+
+ Your `repair-wheel-command` is expected to place the repaired
+ wheel in the {dest_dir} directory. See the documentation for
+ example configurations:
+
+ https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command
+ """
+ )
+ super().__init__(message)
+ self.return_code = 8
diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py
index 9af58c9d..897b5f04 100644
--- a/cibuildwheel/linux.py
+++ b/cibuildwheel/linux.py
@@ -326,6 +326,9 @@ def build_in_container(
repaired_wheels = container.glob(repaired_wheel_dir, "*.whl")
+ if not repaired_wheels:
+ raise errors.RepairStepProducedNoWheelError()
+
for repaired_wheel in repaired_wheels:
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py
index 2a65da73..f830dbcc 100644
--- a/cibuildwheel/macos.py
+++ b/cibuildwheel/macos.py
@@ -551,7 +551,10 @@ def build(options: Options, tmp_path: Path) -> None:
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
- repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
+ try:
+ repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
+ except StopIteration:
+ raise errors.RepairStepProducedNoWheelError() from None
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py
index 909a022c..449be810 100644
--- a/cibuildwheel/windows.py
+++ b/cibuildwheel/windows.py
@@ -490,7 +490,10 @@ def build(options: Options, tmp_path: Path) -> None:
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
- repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
+ try:
+ repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
+ except StopIteration:
+ raise errors.RepairStepProducedNoWheelError() from None
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
diff --git a/docs/options.md b/docs/options.md
index fc3b95e8..37f54bfa 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -1120,6 +1120,7 @@ Platform-specific environment variables are also available:
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}
```
@@ -1158,7 +1159,10 @@ Platform-specific environment variables are also available:
"pipx run abi3audit --strict --report {wheel}",
]
[tool.cibuildwheel.windows]
- repair-wheel-command = "pipx run abi3audit --strict --report {wheel}"
+ repair-wheel-command = [
+ "copy {wheel} {dest_dir}",
+ "pipx run abi3audit --strict --report {wheel}",
+ ]
```
In configuration mode, you can use an inline array, and the items will be joined with `&&`.