mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-10 20:13:46 -06:00
fix: name and announce every narrowed pack
This commit is contained in:
1 parent
bdf6bf6da0
commit
36a284b93b
4 files changed
+138
-26
No files matched your search
+44
-24
@@ -316,6 +316,7 @@ def _narrowings(
|
|||||||
target_name: str | None,
|
target_name: str | None,
|
||||||
one_per_slot: bool,
|
one_per_slot: bool,
|
||||||
required_only: bool,
|
required_only: bool,
|
||||||
|
standalone: bool = False,
|
||||||
) -> list[tuple[str, str]]:
|
) -> list[tuple[str, str]]:
|
||||||
"""Every dimension that narrows a pack, as (filename tag, plain label).
|
"""Every dimension that narrows a pack, as (filename tag, plain label).
|
||||||
|
|
||||||
@@ -342,6 +343,8 @@ def _narrowings(
|
|||||||
)
|
)
|
||||||
if one_per_slot:
|
if one_per_slot:
|
||||||
applied.append(("_OnePerSlot", "one BIOS per system and region"))
|
applied.append(("_OnePerSlot", "one BIOS per system and region"))
|
||||||
|
if standalone:
|
||||||
|
applied.append(("_Standalone", "standalone mode files only"))
|
||||||
if required_only:
|
if required_only:
|
||||||
applied.append(("_Required", "required files only"))
|
applied.append(("_Required", "required files only"))
|
||||||
return applied
|
return applied
|
||||||
@@ -2168,8 +2171,13 @@ def generate_emulator_pack(
|
|||||||
|
|
||||||
# ZIP naming
|
# ZIP naming
|
||||||
display_names = [p.get("emulator", n).replace(" ", "") for n, p in selected]
|
display_names = [p.get("emulator", n).replace(" ", "") for n, p in selected]
|
||||||
region_tag_str = f"_{region_mod.region_tag(regions)}" if regions else ""
|
narrow_tags = "".join(
|
||||||
zip_name = "_".join(display_names) + f"{region_tag_str}_BIOS_Pack.zip"
|
tag
|
||||||
|
for tag, _label in _narrowings(
|
||||||
|
"full", regions, None, False, required_only, standalone
|
||||||
|
)
|
||||||
|
)
|
||||||
|
zip_name = "_".join(display_names) + f"{narrow_tags}_BIOS_Pack.zip"
|
||||||
zip_path = os.path.join(output_dir, zip_name)
|
zip_path = os.path.join(output_dir, zip_name)
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
@@ -2532,8 +2540,17 @@ def generate_split_packs(
|
|||||||
"""Generate split packs (one ZIP per system or manufacturer)."""
|
"""Generate split packs (one ZIP per system or manufacturer)."""
|
||||||
config = load_platform_config(platform_name, platforms_dir)
|
config = load_platform_config(platform_name, platforms_dir)
|
||||||
platform_display = config.get("platform", platform_name)
|
platform_display = config.get("platform", platform_name)
|
||||||
source_tag = {"platform": "_Platform", "truth": "_Truth"}.get(source, "")
|
# The split directory carries the same tags as the packs inside it, or two
|
||||||
split_dir = os.path.join(output_dir, f"{platform_display.replace(' ', '_')}{source_tag}_Split")
|
# differently narrowed runs write into one another.
|
||||||
|
split_tags = "".join(
|
||||||
|
tag
|
||||||
|
for tag, _label in _narrowings(
|
||||||
|
source, regions, target_name, one_per_slot, required_only
|
||||||
|
)
|
||||||
|
)
|
||||||
|
split_dir = os.path.join(
|
||||||
|
output_dir, f"{platform_display.replace(' ', '_')}{split_tags}_Split"
|
||||||
|
)
|
||||||
os.makedirs(split_dir, exist_ok=True)
|
os.makedirs(split_dir, exist_ok=True)
|
||||||
|
|
||||||
systems = config.get("systems", {})
|
systems = config.get("systems", {})
|
||||||
@@ -2929,19 +2946,19 @@ def _run_manifest_mode(
|
|||||||
target_name=args.target,
|
target_name=args.target,
|
||||||
offline=args.offline,
|
offline=args.offline,
|
||||||
)
|
)
|
||||||
source_suffix = {"platform": "_platform", "truth": "_truth"}.get(source, "")
|
narrow_suffix = "".join(
|
||||||
req_suffix = "_required" if required_only else ""
|
tag.lower()
|
||||||
rgn = getattr(args, "regions", None)
|
for tag, _label in _narrowings(
|
||||||
region_suffix = (
|
source,
|
||||||
f"_{region_mod.region_tag(rgn).lower()}" if rgn else ""
|
getattr(args, "regions", None),
|
||||||
)
|
args.target,
|
||||||
target_suffix = (
|
False,
|
||||||
f"_{_target_tag(args.target).lower()}" if args.target else ""
|
required_only,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out_path = os.path.join(
|
out_path = os.path.join(
|
||||||
args.output_dir,
|
args.output_dir,
|
||||||
f"{representative}{source_suffix}{region_suffix}"
|
f"{representative}{narrow_suffix}.json",
|
||||||
f"{target_suffix}{req_suffix}.json",
|
|
||||||
)
|
)
|
||||||
_write_manifest_if_changed(out_path, manifest)
|
_write_manifest_if_changed(out_path, manifest)
|
||||||
print(
|
print(
|
||||||
@@ -2954,8 +2971,7 @@ def _run_manifest_mode(
|
|||||||
if alias_plat != representative:
|
if alias_plat != representative:
|
||||||
alias_path = os.path.join(
|
alias_path = os.path.join(
|
||||||
args.output_dir,
|
args.output_dir,
|
||||||
f"{alias_plat}{source_suffix}{region_suffix}"
|
f"{alias_plat}{narrow_suffix}.json",
|
||||||
f"{target_suffix}{req_suffix}.json",
|
|
||||||
)
|
)
|
||||||
alias_manifest = dict(manifest)
|
alias_manifest = dict(manifest)
|
||||||
alias_manifest["platform"] = alias_plat
|
alias_manifest["platform"] = alias_plat
|
||||||
@@ -3141,17 +3157,19 @@ def _run_platform_packs(
|
|||||||
load_platform_config(p, args.platforms_dir).get("platform", p)
|
load_platform_config(p, args.platforms_dir).get("platform", p)
|
||||||
for p in group_platforms
|
for p in group_platforms
|
||||||
]
|
]
|
||||||
source_tag = {"platform": "_Platform", "truth": "_Truth"}.get(source, "")
|
narrow_tags = "".join(
|
||||||
region_values = getattr(args, "regions", None)
|
tag
|
||||||
rgn_tag = (
|
for tag, _label in _narrowings(
|
||||||
f"_{region_mod.region_tag(region_values)}"
|
source,
|
||||||
if region_values
|
getattr(args, "regions", None),
|
||||||
else ""
|
args.target,
|
||||||
|
args.one_per_slot,
|
||||||
|
required_only,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
req_tag = "_Required" if required_only else ""
|
|
||||||
combined = (
|
combined = (
|
||||||
"_".join(n.replace(" ", "") for n in all_names)
|
"_".join(n.replace(" ", "") for n in all_names)
|
||||||
+ f"{ver_tag}{source_tag}{rgn_tag}{req_tag}_BIOS_Pack.zip"
|
+ f"{ver_tag}{narrow_tags}_BIOS_Pack.zip"
|
||||||
)
|
)
|
||||||
new_path = os.path.join(os.path.dirname(zip_path), combined)
|
new_path = os.path.join(os.path.dirname(zip_path), combined)
|
||||||
if new_path != zip_path:
|
if new_path != zip_path:
|
||||||
@@ -3301,6 +3319,8 @@ def main():
|
|||||||
parser.error(str(exc))
|
parser.error(str(exc))
|
||||||
if args.manifest_targets:
|
if args.manifest_targets:
|
||||||
parser.error("--region is incompatible with --manifest-targets")
|
parser.error("--region is incompatible with --manifest-targets")
|
||||||
|
if args.one_per_slot and args.manifest_targets:
|
||||||
|
parser.error("--one-per-slot is incompatible with --manifest-targets")
|
||||||
|
|
||||||
# Quick-exit modes: --verify-packs alone = verify existing packs only
|
# Quick-exit modes: --verify-packs alone = verify existing packs only
|
||||||
# Combined with --all-variants, generation runs first then verify
|
# Combined with --all-variants, generation runs first then verify
|
||||||
|
|||||||
@@ -5714,6 +5714,66 @@ struct BurnDriver BurnDrvneogeo = {
|
|||||||
self.assertIn("PACK TYPE: Narrowed", readme)
|
self.assertIn("PACK TYPE: Narrowed", readme)
|
||||||
self.assertIn(label, readme)
|
self.assertIn(label, readme)
|
||||||
|
|
||||||
|
def test_no_mode_swallows_a_narrowing_flag(self):
|
||||||
|
"""Every mode either honours a narrowing flag or refuses it.
|
||||||
|
|
||||||
|
Silently ignoring one is how --manifest-targets accepted --region and
|
||||||
|
wrote seven unfiltered manifests, and how --manifest accepted
|
||||||
|
--one-per-slot. A mode that cannot apply a flag must say so.
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
repo = os.path.join(os.path.dirname(__file__), "..")
|
||||||
|
matrix = [
|
||||||
|
(["--manifest-targets"], ["--region", "us"], "refuse"),
|
||||||
|
(["--manifest-targets"], ["--one-per-slot"], "refuse"),
|
||||||
|
(["--platform", "retroarch", "--manifest"], ["--one-per-slot"], "refuse"),
|
||||||
|
(["--emulator", "duckstation"], ["--one-per-slot"], "refuse"),
|
||||||
|
(["--system", "sony-playstation"], ["--one-per-slot"], "refuse"),
|
||||||
|
(["--platform", "retroarch", "--from-md5", "d8f1"], ["--region", "us"],
|
||||||
|
"refuse"),
|
||||||
|
(["--platform", "retroarch", "--from-md5", "d8f1"], ["--one-per-slot"],
|
||||||
|
"refuse"),
|
||||||
|
]
|
||||||
|
for mode, flag, expected in matrix:
|
||||||
|
with self.subTest(mode=mode, flag=flag):
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "scripts/generate_pack.py", *mode, *flag,
|
||||||
|
"--output-dir", os.path.join(self.root, "modecheck")],
|
||||||
|
capture_output=True, text=True, cwd=repo, timeout=120,
|
||||||
|
)
|
||||||
|
combined = result.stdout + result.stderr
|
||||||
|
self.assertNotEqual(
|
||||||
|
result.returncode, 0,
|
||||||
|
f"{' '.join(mode + flag)} was accepted and ignored:\n{combined}",
|
||||||
|
)
|
||||||
|
self.assertIn("error:", combined)
|
||||||
|
|
||||||
|
def test_standalone_mode_names_itself(self):
|
||||||
|
"""--emulator --standalone ships a different file set; without a tag it
|
||||||
|
overwrote the pack built without it."""
|
||||||
|
from generate_pack import _narrowings
|
||||||
|
|
||||||
|
applied = _narrowings("full", None, None, False, False, standalone=True)
|
||||||
|
self.assertEqual([tag for tag, _l in applied], ["_Standalone"])
|
||||||
|
|
||||||
|
def test_every_name_builder_uses_the_same_list(self):
|
||||||
|
"""Four places name a pack or a manifest. None may assemble tags by
|
||||||
|
hand: that is how --one-per-slot reached --split unnamed."""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import generate_pack
|
||||||
|
|
||||||
|
source = inspect.getsource(generate_pack)
|
||||||
|
for hand_rolled in (
|
||||||
|
'req_tag = "_Required"',
|
||||||
|
'source_tag = {"platform": "_Platform"',
|
||||||
|
'req_suffix = "_required"',
|
||||||
|
'slot_tag = "_OnePerSlot"',
|
||||||
|
):
|
||||||
|
self.assertNotIn(hand_rolled, source, hand_rolled)
|
||||||
|
self.assertGreaterEqual(source.count("_narrowings("), 5)
|
||||||
|
|
||||||
def test_a_full_pack_is_not_announced_as_narrowed(self):
|
def test_a_full_pack_is_not_announced_as_narrowed(self):
|
||||||
from generate_pack import _build_readme, _narrowings
|
from generate_pack import _build_readme, _narrowings
|
||||||
|
|
||||||
|
|||||||
@@ -419,5 +419,36 @@ class TestSchemaMatchesModule(unittest.TestCase):
|
|||||||
self.assertEqual(set(node["items"]["enum"]), set(region.REGIONS))
|
self.assertEqual(set(node["items"]["enum"]), set(region.REGIONS))
|
||||||
|
|
||||||
|
|
||||||
|
class TestVerifyModesHonourRegion(unittest.TestCase):
|
||||||
|
"""verify.py must apply --region in every mode, or refuse it.
|
||||||
|
|
||||||
|
It silently ignored the flag in --emulator and --system mode: the report
|
||||||
|
was identical with and without, so a filtered pack could not be checked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _run(self, *args: str) -> str:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
repo = os.path.join(os.path.dirname(__file__), "..")
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, "scripts/verify.py", *args],
|
||||||
|
capture_output=True, text=True, cwd=repo, timeout=900,
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
def test_emulator_mode_narrows(self):
|
||||||
|
plain = self._run("--emulator", "duckstation")
|
||||||
|
filtered = self._run("--emulator", "duckstation", "--region", "us")
|
||||||
|
if "duckstation" not in plain:
|
||||||
|
self.skipTest("duckstation profile not present")
|
||||||
|
self.assertNotEqual(plain, filtered)
|
||||||
|
|
||||||
|
def test_system_mode_narrows(self):
|
||||||
|
plain = self._run("--system", "sony-playstation")
|
||||||
|
filtered = self._run("--system", "sony-playstation", "--region", "us")
|
||||||
|
if not plain.strip():
|
||||||
|
self.skipTest("system not present")
|
||||||
|
self.assertNotEqual(plain, filtered)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -158,8 +158,9 @@ selects by hash. `pipeline.py` never passes it, so the released packs stay
|
|||||||
complete.
|
complete.
|
||||||
|
|
||||||
Every dimension that removes files appears in the output filename and in the
|
Every dimension that removes files appears in the output filename and in the
|
||||||
pack README, because both are built from the same list (`_narrowings` in
|
pack README, because the five places that name an artefact (the pack, the
|
||||||
`generate_pack.py`). A pack narrowed three ways is called
|
`--split` rename, the `_Split` directory, the grouped-alias rename and the
|
||||||
|
manifest) all build from one list (`_narrowings` in `generate_pack.py`). A pack narrowed three ways is called
|
||||||
`Recalbox_10.0.8_NorthAmerica_OnePerSlot_Required_BIOS_Pack.zip` and opens with
|
`Recalbox_10.0.8_NorthAmerica_OnePerSlot_Required_BIOS_Pack.zip` and opens with
|
||||||
a `PACK TYPE: Narrowed` block listing the three. Adding a new way to narrow a
|
a `PACK TYPE: Narrowed` block listing the three. Adding a new way to narrow a
|
||||||
pack means adding it to that list, so it cannot reach users nameless or
|
pack means adding it to that list, so it cannot reach users nameless or
|
||||||
|
|||||||
Reference in new issue
Block a user