|
27 | 27 | from _helper_functions import print_color |
28 | 28 |
|
29 | 29 |
|
| 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 | + |
30 | 58 | def _dedupe_wheel_paths(wheels_dir: Path) -> List[Path]: |
31 | 59 | """Collect *.whl under wheels_dir once per inode (rglob can list the same file twice via symlinks).""" |
32 | 60 | wheels: List[Path] = [] |
@@ -209,10 +237,10 @@ def main() -> None: |
209 | 237 | skipped_count += 1 |
210 | 238 | continue |
211 | 239 |
|
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) |
216 | 244 | wheel.unlink(missing_ok=True) |
217 | 245 | deleted_count += 1 |
218 | 246 | continue |
@@ -241,6 +269,15 @@ def main() -> None: |
241 | 269 | # Check for errors |
242 | 270 | error_msg = result.stderr.strip() if result.stderr else "" |
243 | 271 |
|
| 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 | + |
244 | 281 | # Special handling for incorrectly tagged universal2 wheels on macOS |
245 | 282 | if ( |
246 | 283 | current_platform == "Darwin" |
@@ -350,8 +387,8 @@ def main() -> None: |
350 | 387 | repaired.rename(wheel) |
351 | 388 | final_path = wheel |
352 | 389 | 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) |
355 | 392 | final_path.unlink(missing_ok=True) |
356 | 393 | deleted_count += 1 |
357 | 394 | else: |
|
0 commit comments