Skip to content

Commit d023d68

Browse files
committed
fix: BadZip logic fixed and enhanced
1 parent 1a631a2 commit d023d68

2 files changed

Lines changed: 72 additions & 10 deletions

File tree

repair_wheels.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,34 @@
2727
from _helper_functions import print_color
2828

2929

30+
def _wheel_archive_is_valid(path: Path) -> bool:
31+
"""Return True only if the file is a readable zip with a valid central directory.
32+
33+
zipfile.is_zipfile() only checks the local header magic; wheels can still be
34+
truncated or corrupt such that opening fails with BadZipFile (e.g. bad central
35+
directory magic) — which delocate then surfaces.
36+
"""
37+
if not zipfile.is_zipfile(path):
38+
return False
39+
try:
40+
with zipfile.ZipFile(path, "r") as zf:
41+
zf.namelist()
42+
except zipfile.BadZipFile:
43+
return False
44+
return True
45+
46+
47+
def _stderr_indicates_bad_zip(error_msg: str) -> bool:
48+
"""True if repair tool output indicates an unreadable/corrupt zip archive."""
49+
if not error_msg:
50+
return False
51+
return (
52+
"BadZipFile" in error_msg
53+
or "Bad magic number for central directory" in error_msg
54+
or "File is not a zip file" in error_msg
55+
)
56+
57+
3058
def _dedupe_wheel_paths(wheels_dir: Path) -> List[Path]:
3159
"""Collect *.whl under wheels_dir once per inode (rglob can list the same file twice via symlinks)."""
3260
wheels: List[Path] = []
@@ -209,10 +237,10 @@ def main() -> None:
209237
skipped_count += 1
210238
continue
211239

212-
# PEP 427: wheels are zip files; invalid magic usually means truncated/corrupt CI artifact
213-
if not zipfile.is_zipfile(wheel):
214-
print_color(" -> Deleting file (not a valid zip / wheel archive)", Fore.RED)
215-
# missing_ok: duplicate paths or prior partial runs can leave nothing to remove
240+
# PEP 427: wheels are zip files; truncated/corrupt CI artifacts may pass is_zipfile
241+
# but fail on central directory (delocate: BadZipFile).
242+
if not _wheel_archive_is_valid(wheel):
243+
print_color(" -> Deleting file (not a valid / readable zip wheel archive)", Fore.RED)
216244
wheel.unlink(missing_ok=True)
217245
deleted_count += 1
218246
continue
@@ -241,6 +269,15 @@ def main() -> None:
241269
# Check for errors
242270
error_msg = result.stderr.strip() if result.stderr else ""
243271

272+
# Corrupt zip / bad central directory (delocate opens the wheel as a zip)
273+
if _stderr_indicates_bad_zip(error_msg):
274+
print_color(" -> Deleting file (repair tool reported corrupt zip archive)", Fore.RED)
275+
for old_wheel in temp_dir.glob("*.whl"):
276+
old_wheel.unlink()
277+
wheel.unlink(missing_ok=True)
278+
deleted_count += 1
279+
continue
280+
244281
# Special handling for incorrectly tagged universal2 wheels on macOS
245282
if (
246283
current_platform == "Darwin"
@@ -350,8 +387,8 @@ def main() -> None:
350387
repaired.rename(wheel)
351388
final_path = wheel
352389
print_color(f" -> Repaired successfully: {repaired.name}", Fore.GREEN)
353-
if not zipfile.is_zipfile(final_path):
354-
print_color(" -> Deleting repaired output (not a valid zip archive)", Fore.RED)
390+
if not _wheel_archive_is_valid(final_path):
391+
print_color(" -> Deleting repaired output (not a valid / readable zip archive)", Fore.RED)
355392
final_path.unlink(missing_ok=True)
356393
deleted_count += 1
357394
else:

test_wheels_install.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@
3434
WHEELS_DIR = Path("./downloaded_wheels")
3535

3636

37+
def wheel_archive_is_readable(path: Path) -> bool:
38+
"""True if the file is a zip with a readable central directory (a valid wheel container).
39+
40+
zipfile.is_zipfile() only checks the leading magic; truncated or corrupt wheels can still
41+
fail at install with pip's ``Wheel '...' ... is invalid`` (BadZipFile inside pip).
42+
"""
43+
if not zipfile.is_zipfile(path):
44+
return False
45+
try:
46+
with zipfile.ZipFile(path, "r") as zf:
47+
zf.namelist()
48+
except zipfile.BadZipFile:
49+
return False
50+
return True
51+
52+
3753
def get_python_version_tag() -> str:
3854
"""Get the Python version tag (e.g., '311' for Python 3.11)."""
3955
return f"{sys.version_info.major}{sys.version_info.minor}"
@@ -148,9 +164,15 @@ def is_compatibility_error(error_message: str) -> bool:
148164

149165
def is_corrupt_wheel_archive_error(error_message: str) -> bool:
150166
"""True if pip failed because the file is not a readable ZIP / wheel archive."""
167+
if not error_message:
168+
return False
169+
# pip.exceptions.InvalidWheel -> "Wheel 'pkg' located at <path> is invalid."
170+
if "Wheel '" in error_message and " is invalid." in error_message:
171+
return True
151172
markers = (
152173
"BadZipFile",
153174
"Bad magic number for file header",
175+
"Bad magic number for central directory",
154176
"has an invalid wheel",
155177
"zipfile.BadZipFile",
156178
)
@@ -216,9 +238,12 @@ def main() -> int:
216238
print_color("---------- INSTALL WHEELS ----------")
217239

218240
for wheel_path in wheels_to_install:
219-
if not zipfile.is_zipfile(wheel_path):
241+
if not wheel_archive_is_readable(wheel_path):
220242
discarded_corrupt += 1
221-
discard_corrupt_wheel(wheel_path, "invalid zip — not a valid wheel file (PEP 427)")
243+
discard_corrupt_wheel(
244+
wheel_path,
245+
"unreadable / corrupt zip — not a valid wheel archive (PEP 427)",
246+
)
222247
continue
223248

224249
success, error_message = install_wheel(wheel_path)
@@ -233,8 +258,8 @@ def main() -> int:
233258
wheel_path.unlink()
234259
print_color(f"-- {wheel_path.name} (compatibility constraint)", Fore.YELLOW)
235260
elif is_corrupt_wheel_archive_error(error_message):
236-
# Truncated/corrupt artifact or bad repair output; same handling as incompatible:
237-
# drop from this test artifact so CI can continue (see module docstring).
261+
# Truncated/corrupt artifact or bad repair output; drop from this test artifact
262+
# so CI can continue (see module docstring).
238263
discarded_corrupt += 1
239264
discard_corrupt_wheel(wheel_path, "invalid / corrupt zip (pip could not read wheel)")
240265
else:

0 commit comments

Comments
 (0)