diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 7634b7cb6..0aa618493 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1645,6 +1645,9 @@ def generate_emulator_pack( ) -> str | None: """Generate a ZIP pack for specific emulator profiles. + Returns the path, "" when no file is needed (verify --emulator reports + 0/0 OK), None on failure. + `pack_name` is the caller's own file name: the system pack names its archive after the system, and joining 23 SNES emulator names went past the 255 bytes a file name may hold. `system_ids` keeps only the entries @@ -1986,10 +1989,16 @@ def generate_emulator_pack( ) # Remove empty ZIP (no files packed and no missing = nothing to ship) - if record is None and total_files + data_files == 0 and not ( + nothing_needed = total_files + data_files == 0 and not ( missing_files or rejected_files or instruction_files or unsourceable_files - ): + ) + if record is None and nothing_needed: os.unlink(zip_path) + # What verify --emulator reports as 0/0 OK: no file is needed, so + # no pack is written and the run succeeds. Printed with the path, + # the run named a file it had just removed and exited 1. + print(f" {', '.join(display_names)}: no file needed, no pack written") + return "" # Report. The members of data trees are counted apart: verify --emulator # counts the profile's files, and folding 3500 tree members into the @@ -2046,7 +2055,10 @@ def generate_system_pack( offline: bool | None = None, platforms_dir: str | None = None, ) -> str | None: - """Generate a ZIP pack for all emulators supporting given system IDs.""" + """Generate a ZIP pack for all emulators supporting given system IDs. + + Returns the path, "" when no file is needed, None on failure. + """ profiles = load_emulator_profiles(emulators_dir) matching = profiles_for_systems(profiles, system_ids, standalone) @@ -2107,7 +2119,7 @@ def generate_system_pack( final_dir=output_dir, ) if not result: - return None + return result new_path = os.path.join(output_dir, final_name) os.replace(result, new_path) return new_path @@ -3757,7 +3769,7 @@ def _main(held: contextlib.ExitStack) -> None: offline=args.offline, platforms_dir=args.platforms_dir, ) - if not built: + if built is None: sys.exit(1) return @@ -3778,7 +3790,7 @@ def _main(held: contextlib.ExitStack) -> None: offline=args.offline, platforms_dir=args.platforms_dir, ) - if not built: + if built is None: sys.exit(1) return diff --git a/tests/test_e2e.py b/tests/test_e2e.py index b05a93682..0093dcda5 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -6374,6 +6374,27 @@ struct BurnDriver BurnDrvneogeo = { [f for f in os.listdir(out) if f.endswith(".zip")] if os.path.isdir(out) else [] ) + def test_an_emulator_needing_no_file_succeeds_like_verify(self): + """Handy requires no file. --required-only removed the empty ZIP, + printed its path and exited 1, while verify reported 0/0 OK.""" + import subprocess + + repo = os.path.join(os.path.dirname(__file__), "..") + out = os.path.join(self.root, "no_file_needed") + proc = subprocess.run( + [sys.executable, "scripts/generate_pack.py", "--emulator", "handy", + "--required-only", "--offline", "--output-dir", out], + capture_output=True, text=True, cwd=repo, timeout=300, + ) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + self.assertIn("no file needed, no pack written", proc.stdout) + self.assertNotIn(".zip", proc.stdout) + verify = subprocess.run( + [sys.executable, "scripts/verify.py", "--emulator", "handy", "--required-only"], + capture_output=True, text=True, cwd=repo, timeout=300, + ) + self.assertEqual(verify.returncode, proc.returncode) + def test_a_platform_without_cores_refuses_profile_narrowings(self): """MiSTer and BizHawk declare cores: []. --region, --one-per-slot and --no-game-data read the profiles, so their packs withdrew