mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-17 22:32:31 -05:00
feat: add --split flag for per-system packs
This commit is contained in:
@@ -891,6 +891,67 @@ def list_platforms(platforms_dir: str) -> list[str]:
|
|||||||
return list_registered_platforms(platforms_dir, include_archived=True)
|
return list_registered_platforms(platforms_dir, include_archived=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _system_display_name(system_id: str) -> str:
|
||||||
|
"""Convert system ID to display name for ZIP naming."""
|
||||||
|
s = system_id.lower().replace("_", "-")
|
||||||
|
for prefix in MANUFACTURER_PREFIXES:
|
||||||
|
if s.startswith(prefix):
|
||||||
|
s = s[len(prefix):]
|
||||||
|
break
|
||||||
|
parts = s.split("-")
|
||||||
|
return "_".join(p.title() for p in parts if p)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_split_packs(
|
||||||
|
platform_name: str,
|
||||||
|
platforms_dir: str,
|
||||||
|
db: dict,
|
||||||
|
bios_dir: str,
|
||||||
|
output_dir: str,
|
||||||
|
group_by: str = "system",
|
||||||
|
emulators_dir: str = "emulators",
|
||||||
|
zip_contents: dict | None = None,
|
||||||
|
data_registry: dict | None = None,
|
||||||
|
emu_profiles: dict | None = None,
|
||||||
|
target_cores: set[str] | None = None,
|
||||||
|
required_only: bool = False,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Generate split packs (one ZIP per system or manufacturer)."""
|
||||||
|
config = load_platform_config(platform_name, platforms_dir)
|
||||||
|
platform_display = config.get("platform", platform_name)
|
||||||
|
split_dir = os.path.join(output_dir, f"{platform_display.replace(' ', '_')}_Split")
|
||||||
|
os.makedirs(split_dir, exist_ok=True)
|
||||||
|
|
||||||
|
systems = config.get("systems", {})
|
||||||
|
|
||||||
|
if group_by == "manufacturer":
|
||||||
|
groups = _group_systems_by_manufacturer(systems, db, bios_dir)
|
||||||
|
else:
|
||||||
|
groups = {_system_display_name(sid): [sid] for sid in systems}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for group_name, group_system_ids in sorted(groups.items()):
|
||||||
|
zip_path = generate_pack(
|
||||||
|
platform_name, platforms_dir, db, bios_dir, split_dir,
|
||||||
|
emulators_dir=emulators_dir, zip_contents=zip_contents,
|
||||||
|
data_registry=data_registry, emu_profiles=emu_profiles,
|
||||||
|
target_cores=target_cores, required_only=required_only,
|
||||||
|
system_filter=group_system_ids,
|
||||||
|
)
|
||||||
|
if zip_path:
|
||||||
|
version = config.get("version", config.get("dat_version", ""))
|
||||||
|
ver_tag = f"_{version.replace(' ', '')}" if version else ""
|
||||||
|
req_tag = "_Required" if required_only else ""
|
||||||
|
new_name = f"{platform_display.replace(' ', '_')}{ver_tag}{req_tag}_{group_name}_BIOS_Pack.zip"
|
||||||
|
new_path = os.path.join(split_dir, new_name)
|
||||||
|
if new_path != zip_path:
|
||||||
|
os.rename(zip_path, new_path)
|
||||||
|
zip_path = new_path
|
||||||
|
results.append(zip_path)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Generate platform BIOS ZIP packs")
|
parser = argparse.ArgumentParser(description="Generate platform BIOS ZIP packs")
|
||||||
parser.add_argument("--platform", "-p", help="Platform name (e.g., retroarch)")
|
parser.add_argument("--platform", "-p", help="Platform name (e.g., retroarch)")
|
||||||
@@ -915,6 +976,11 @@ def main():
|
|||||||
parser.add_argument("--list", action="store_true", help="List available platforms")
|
parser.add_argument("--list", action="store_true", help="List available platforms")
|
||||||
parser.add_argument("--required-only", action="store_true",
|
parser.add_argument("--required-only", action="store_true",
|
||||||
help="Only include required files, skip optional")
|
help="Only include required files, skip optional")
|
||||||
|
parser.add_argument("--split", action="store_true",
|
||||||
|
help="Generate one ZIP per system/manufacturer")
|
||||||
|
parser.add_argument("--group-by", choices=["system", "manufacturer"],
|
||||||
|
default="system",
|
||||||
|
help="Grouping for --split (default: system)")
|
||||||
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
|
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
|
||||||
parser.add_argument("--list-targets", action="store_true", help="List available targets for the platform")
|
parser.add_argument("--list-targets", action="store_true", help="List available targets for the platform")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -962,6 +1028,12 @@ def main():
|
|||||||
parser.error("Specify --platform, --all, --emulator, or --system")
|
parser.error("Specify --platform, --all, --emulator, or --system")
|
||||||
if args.standalone and not (has_emulator or (has_system and not has_platform and not has_all)):
|
if args.standalone and not (has_emulator or (has_system and not has_platform and not has_all)):
|
||||||
parser.error("--standalone requires --emulator or --system (without --platform)")
|
parser.error("--standalone requires --emulator or --system (without --platform)")
|
||||||
|
if args.split and not (has_platform or has_all):
|
||||||
|
parser.error("--split requires --platform or --all")
|
||||||
|
if args.split and has_emulator:
|
||||||
|
parser.error("--split is incompatible with --emulator")
|
||||||
|
if args.group_by != "system" and not args.split:
|
||||||
|
parser.error("--group-by requires --split")
|
||||||
if args.target and not (has_platform or has_all):
|
if args.target and not (has_platform or has_all):
|
||||||
parser.error("--target requires --platform or --all")
|
parser.error("--target requires --platform or --all")
|
||||||
if args.target and has_emulator:
|
if args.target and has_emulator:
|
||||||
@@ -1054,15 +1126,25 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
tc = target_cores_cache.get(representative) if args.target else None
|
tc = target_cores_cache.get(representative) if args.target else None
|
||||||
zip_path = generate_pack(
|
if args.split:
|
||||||
representative, args.platforms_dir, db, args.bios_dir, args.output_dir,
|
zip_paths = generate_split_packs(
|
||||||
include_extras=args.include_extras, emulators_dir=args.emulators_dir,
|
representative, args.platforms_dir, db, args.bios_dir,
|
||||||
zip_contents=zip_contents, data_registry=data_registry,
|
args.output_dir, group_by=args.group_by,
|
||||||
emu_profiles=emu_profiles, target_cores=tc,
|
emulators_dir=args.emulators_dir, zip_contents=zip_contents,
|
||||||
required_only=args.required_only,
|
data_registry=data_registry, emu_profiles=emu_profiles,
|
||||||
system_filter=system_filter,
|
target_cores=tc, required_only=args.required_only,
|
||||||
)
|
)
|
||||||
if zip_path and variants:
|
print(f" Split into {len(zip_paths)} packs")
|
||||||
|
else:
|
||||||
|
zip_path = generate_pack(
|
||||||
|
representative, args.platforms_dir, db, args.bios_dir, args.output_dir,
|
||||||
|
include_extras=args.include_extras, emulators_dir=args.emulators_dir,
|
||||||
|
zip_contents=zip_contents, data_registry=data_registry,
|
||||||
|
emu_profiles=emu_profiles, target_cores=tc,
|
||||||
|
required_only=args.required_only,
|
||||||
|
system_filter=system_filter,
|
||||||
|
)
|
||||||
|
if not args.split and zip_path and variants:
|
||||||
rep_cfg = load_platform_config(representative, args.platforms_dir)
|
rep_cfg = load_platform_config(representative, args.platforms_dir)
|
||||||
ver = rep_cfg.get("version", rep_cfg.get("dat_version", ""))
|
ver = rep_cfg.get("version", rep_cfg.get("dat_version", ""))
|
||||||
ver_tag = f"_{ver.replace(' ', '')}" if ver else ""
|
ver_tag = f"_{ver.replace(' ', '')}" if ver else ""
|
||||||
|
|||||||
@@ -1681,5 +1681,90 @@ class TestE2E(unittest.TestCase):
|
|||||||
self.assertIn("2 files", output)
|
self.assertIn("2 files", output)
|
||||||
|
|
||||||
|
|
||||||
|
def test_135_split_by_system(self):
|
||||||
|
"""--split generates one ZIP per system in a subdirectory."""
|
||||||
|
import tempfile
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
plat_dir = os.path.join(tmpdir, "platforms")
|
||||||
|
os.makedirs(plat_dir)
|
||||||
|
bios_dir = os.path.join(tmpdir, "bios", "Test")
|
||||||
|
os.makedirs(os.path.join(bios_dir, "SysA"))
|
||||||
|
os.makedirs(os.path.join(bios_dir, "SysB"))
|
||||||
|
emu_dir = os.path.join(tmpdir, "emulators")
|
||||||
|
os.makedirs(emu_dir)
|
||||||
|
out_dir = os.path.join(tmpdir, "dist")
|
||||||
|
|
||||||
|
file_a = os.path.join(bios_dir, "SysA", "bios_a.bin")
|
||||||
|
file_b = os.path.join(bios_dir, "SysB", "bios_b.bin")
|
||||||
|
with open(file_a, "wb") as f:
|
||||||
|
f.write(b"system_a")
|
||||||
|
with open(file_b, "wb") as f:
|
||||||
|
f.write(b"system_b")
|
||||||
|
|
||||||
|
from common import compute_hashes
|
||||||
|
ha = compute_hashes(file_a)
|
||||||
|
hb = compute_hashes(file_b)
|
||||||
|
|
||||||
|
db = {
|
||||||
|
"files": {
|
||||||
|
ha["sha1"]: {"name": "bios_a.bin", "md5": ha["md5"],
|
||||||
|
"sha1": ha["sha1"], "sha256": ha["sha256"],
|
||||||
|
"path": file_a,
|
||||||
|
"paths": [file_a]},
|
||||||
|
hb["sha1"]: {"name": "bios_b.bin", "md5": hb["md5"],
|
||||||
|
"sha1": hb["sha1"], "sha256": hb["sha256"],
|
||||||
|
"path": file_b,
|
||||||
|
"paths": [file_b]},
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"by_md5": {ha["md5"]: ha["sha1"], hb["md5"]: hb["sha1"]},
|
||||||
|
"by_name": {"bios_a.bin": [ha["sha1"]], "bios_b.bin": [hb["sha1"]]},
|
||||||
|
"by_crc32": {}, "by_path_suffix": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registry = {"platforms": {"splitplat": {"status": "active"}}}
|
||||||
|
with open(os.path.join(plat_dir, "_registry.yml"), "w") as f:
|
||||||
|
yaml.dump(registry, f)
|
||||||
|
plat_cfg = {
|
||||||
|
"platform": "SplitTest",
|
||||||
|
"verification_mode": "existence",
|
||||||
|
"systems": {
|
||||||
|
"test-system-a": {"files": [{"name": "bios_a.bin", "sha1": ha["sha1"]}]},
|
||||||
|
"test-system-b": {"files": [{"name": "bios_b.bin", "sha1": hb["sha1"]}]},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(os.path.join(plat_dir, "splitplat.yml"), "w") as f:
|
||||||
|
yaml.dump(plat_cfg, f)
|
||||||
|
|
||||||
|
from generate_pack import generate_split_packs
|
||||||
|
from common import build_zip_contents_index, load_emulator_profiles
|
||||||
|
zip_contents = build_zip_contents_index(db)
|
||||||
|
emu_profiles = load_emulator_profiles(emu_dir)
|
||||||
|
|
||||||
|
zip_paths = generate_split_packs(
|
||||||
|
"splitplat", plat_dir, db, os.path.join(tmpdir, "bios"), out_dir,
|
||||||
|
emulators_dir=emu_dir, zip_contents=zip_contents,
|
||||||
|
emu_profiles=emu_profiles, group_by="system",
|
||||||
|
)
|
||||||
|
self.assertEqual(len(zip_paths), 2)
|
||||||
|
|
||||||
|
# Check subdirectory exists
|
||||||
|
split_dir = os.path.join(out_dir, "SplitTest_Split")
|
||||||
|
self.assertTrue(os.path.isdir(split_dir))
|
||||||
|
|
||||||
|
# Verify each ZIP contains only its system's files
|
||||||
|
for zp in zip_paths:
|
||||||
|
with zipfile.ZipFile(zp) as zf:
|
||||||
|
names = zf.namelist()
|
||||||
|
basename = os.path.basename(zp)
|
||||||
|
if "System_A" in basename:
|
||||||
|
self.assertIn("bios_a.bin", names)
|
||||||
|
self.assertNotIn("bios_b.bin", names)
|
||||||
|
elif "System_B" in basename:
|
||||||
|
self.assertIn("bios_b.bin", names)
|
||||||
|
self.assertNotIn("bios_a.bin", names)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user