mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
fix: record each platform's own list version
install/<platform>.json wrote the constant "1.0" as the version of the list it was built from. The manifest now carries the version the platform YAML records, and generate_pack refuses a YAML without one. inherits: no longer passes a parent's version to a child: Lakka was published as RetroArch v1.22.2 in its manifest, pack name and site page. lakka.yml records v6.1, the Lakka-LibreELEC tag its target list is read at. The MiSTer scraper refuses a BiosDB without a timestamp instead of writing an empty version. A pack's manifest.json records list_version, so an install from the pack alone knows it too. Manifests regenerated twice and compared: only version and generated changed.
This commit is contained in:
1 parent
1f6c018006
commit
25c356669b
37 files changed
+197
-52
No files matched your search
@@ -2080,6 +2080,59 @@ def _git_paths(*args: str, stdin: list[str] | None = None) -> set[str] | None:
|
||||
return {p for p in result.stdout.split("\0") if p}
|
||||
|
||||
|
||||
class ManifestVersionIsThePlatformsOwn(unittest.TestCase):
|
||||
"""Every install manifest named its list "1.0", and an inheriting YAML
|
||||
took its parent's version: Lakka was published as RetroArch v1.22.2.
|
||||
The installer compares the list's version with the one a machine runs,
|
||||
so each manifest states the version its own platform YAML records."""
|
||||
|
||||
def setUp(self):
|
||||
from scripts.common import _platform_config_cache
|
||||
_platform_config_cache.clear()
|
||||
|
||||
def test_committed_manifests_state_their_platform_version(self):
|
||||
from scripts.common import load_platform_config
|
||||
manifests = sorted((ROOT / "install").glob("*.json"))
|
||||
self.assertTrue(manifests)
|
||||
for path in manifests:
|
||||
with self.subTest(manifest=path.name):
|
||||
data = json.loads(path.read_text())
|
||||
config = load_platform_config(data["platform"], str(ROOT / "platforms"))
|
||||
self.assertTrue(config.get("version"))
|
||||
self.assertEqual(data["version"], str(config["version"]))
|
||||
|
||||
def test_no_child_shows_its_parents_version(self):
|
||||
from scripts.common import load_platform_config
|
||||
platforms = ROOT / "platforms"
|
||||
children = {}
|
||||
for path in sorted(platforms.glob("*.yml")):
|
||||
raw = yaml.safe_load(path.read_text()) or {}
|
||||
if isinstance(raw, dict) and raw.get("inherits"):
|
||||
children[path.stem] = raw
|
||||
self.assertIn("lakka", children)
|
||||
for name, raw in children.items():
|
||||
with self.subTest(platform=name):
|
||||
parent = yaml.safe_load(
|
||||
(platforms / f"{raw['inherits']}.yml").read_text())
|
||||
version = load_platform_config(name, str(platforms)).get("version")
|
||||
self.assertEqual(version, raw.get("version"))
|
||||
self.assertNotEqual(version, parent.get("version"))
|
||||
|
||||
def test_an_unversioned_child_has_no_version(self):
|
||||
from scripts.common import load_platform_config
|
||||
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as tmp:
|
||||
Path(tmp, "parent.yml").write_text("platform: P\nversion: '2.0'\nsystems: {}\n")
|
||||
Path(tmp, "child.yml").write_text("inherits: parent\nplatform: C\n")
|
||||
self.assertNotIn("version", load_platform_config("child", tmp))
|
||||
|
||||
def test_lakka_names_the_release_its_cores_are_read_at(self):
|
||||
platforms = ROOT / "platforms"
|
||||
lakka = yaml.safe_load((platforms / "lakka.yml").read_text())
|
||||
targets = yaml.safe_load((platforms / "targets" / "lakka.yml").read_text())
|
||||
tag = re.search(r"/blob/([^/]+)/", targets["source"]).group(1)
|
||||
self.assertEqual(lakka["version"], tag)
|
||||
|
||||
|
||||
class EveryManifestEntryIsFetchable(unittest.TestCase):
|
||||
"""An install manifest may not list a file the installer cannot get.
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class ACoreFileFillsAFreeDeclaration(unittest.TestCase):
|
||||
self.emulators.mkdir()
|
||||
(self.platforms / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat",
|
||||
"version": "2.1",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "system",
|
||||
"cores": ["fakecore"],
|
||||
@@ -118,7 +119,7 @@ class AFileDeclaredUnderAnotherName(unittest.TestCase):
|
||||
(root / "platforms").mkdir()
|
||||
(root / "emulators").mkdir()
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "existence",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": ["gs"],
|
||||
"systems": {"apple-iigs": {"files": [
|
||||
{"name": "ROM1", "destination": "ROM1", "md5": md5},
|
||||
@@ -156,7 +157,7 @@ class AMissingDataDirectoryFailsTheRun(unittest.TestCase):
|
||||
(root / "emulators").mkdir()
|
||||
(root / "bios").mkdir()
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "existence",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": [],
|
||||
"systems": {"console-a": {"files": [], "data_directories": [
|
||||
{"ref": "assets", "destination": "assets"},
|
||||
@@ -197,7 +198,7 @@ class AFileNobodyHoldsIsAnOmission(unittest.TestCase):
|
||||
for folder in ("platforms", "emulators", "bios"):
|
||||
(root / folder).mkdir()
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "existence",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": ["devil"],
|
||||
"systems": {"diablo": {"files": []}},
|
||||
}))
|
||||
@@ -238,7 +239,7 @@ class AMemberNameIsNotALooseFile(unittest.TestCase):
|
||||
(root / "platforms").mkdir()
|
||||
(root / "emulators").mkdir()
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "existence",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": ["naomi2core"],
|
||||
"systems": {"sega-naomi2": {"files": []}},
|
||||
}))
|
||||
@@ -283,7 +284,7 @@ class TwoDeclarationsOfOnePath(unittest.TestCase):
|
||||
for folder in ("platforms", "emulators"):
|
||||
(root / folder).mkdir()
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "existence",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": [],
|
||||
"systems": {
|
||||
"a-system": {"files": [{"name": first, "destination": "shared.bin"}]},
|
||||
@@ -335,7 +336,7 @@ class ANamelessWhitelistEntry(unittest.TestCase):
|
||||
(root / folder).mkdir()
|
||||
values = [md5.upper(), f"{'0' * 32},{md5}", "f" * 32]
|
||||
(root / "platforms" / "fakeplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "FakePlat", "verification_mode": "md5",
|
||||
"platform": "FakePlat", "version": "2.1", "verification_mode": "md5",
|
||||
"base_destination": "bios", "cores": [],
|
||||
"systems": {"sony-playstation": {"files": [
|
||||
{"name": f"sony-playstation:{v}", "destination": "", "md5": v}
|
||||
|
||||
@@ -76,7 +76,7 @@ class _Collection(unittest.TestCase):
|
||||
}}
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
self.config = {
|
||||
"platform": "Exist", "verification_mode": "existence",
|
||||
"platform": "Exist", "version": "2.1", "verification_mode": "existence",
|
||||
"cores": ["sized", "unsized"],
|
||||
"systems": {"mac": {"files": []}, "iigs": {"files": []}},
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ files:
|
||||
|
||||
PLATFORM = {
|
||||
"platform": "Demo",
|
||||
"version": "2.1",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "",
|
||||
"cores": ["demo"],
|
||||
|
||||
@@ -224,6 +224,7 @@ class TestE2E(unittest.TestCase):
|
||||
def _create_existence_platform(self):
|
||||
config = {
|
||||
"platform": "TestExistence",
|
||||
"version": "9.1",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "system",
|
||||
"systems": {
|
||||
@@ -3676,6 +3677,51 @@ class TestE2E(unittest.TestCase):
|
||||
self.assertIsInstance(f["size"], int)
|
||||
self.assertGreater(len(f["sha1"]), 0)
|
||||
|
||||
def test_91_manifest_version_is_the_platform_list_version(self):
|
||||
"""The manifest names the version of the list it was built from.
|
||||
|
||||
It was the constant "1.0" for every platform, so the installer could
|
||||
not tell which Batocera or Recalbox release a list described. Two
|
||||
platforms of different versions show the value comes from each YAML.
|
||||
A YAML without one, an inheriting child included, is refused rather
|
||||
than written with a stand-in or its parent's version.
|
||||
"""
|
||||
from common import _platform_config_cache
|
||||
from generate_pack import generate_manifest
|
||||
|
||||
registry_path = os.path.join(self.platforms_dir, "_test_registry_version.yml")
|
||||
with open(registry_path, "w") as fh:
|
||||
yaml.dump({"platforms": {}}, fh)
|
||||
with open(os.path.join(self.platforms_dir, "test_md5.yml")) as fh:
|
||||
config = yaml.safe_load(fh)
|
||||
config["version"] = "41"
|
||||
with open(os.path.join(self.platforms_dir, "versioned_md5.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
del config["version"]
|
||||
with open(os.path.join(self.platforms_dir, "unversioned.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
_platform_config_cache.clear()
|
||||
|
||||
def version_of(name: str) -> str:
|
||||
return generate_manifest(
|
||||
name, self.platforms_dir, self.db, self.bios_dir, registry_path,
|
||||
emulators_dir=self.emulators_dir, offline=True,
|
||||
)["version"]
|
||||
|
||||
self.assertEqual(version_of("test_existence"), "9.1")
|
||||
self.assertEqual(version_of("versioned_md5"), "41")
|
||||
for name in ("unversioned", "test_inherited"):
|
||||
with self.subTest(platform=name), \
|
||||
self.assertRaisesRegex(ValueError, "records no version"):
|
||||
version_of(name)
|
||||
with open(os.path.join(self.platforms_dir, "test_inherited.yml")) as fh:
|
||||
child = yaml.safe_load(fh)
|
||||
child["version"] = "2"
|
||||
with open(os.path.join(self.platforms_dir, "test_inherited.yml"), "w") as fh:
|
||||
yaml.dump(child, fh)
|
||||
_platform_config_cache.clear()
|
||||
self.assertEqual(version_of("test_inherited"), "2")
|
||||
|
||||
def test_91b_manifest_hash_points_at_a_fetchable_file(self):
|
||||
"""A declared hash no local file carries must not blank repo_path.
|
||||
|
||||
@@ -5726,6 +5772,7 @@ struct BurnDriver BurnDrvneogeo = {
|
||||
self.db = self._build_db()
|
||||
config = {
|
||||
"platform": "RegionPlat",
|
||||
"version": "3.0",
|
||||
"verification_mode": "existence",
|
||||
"systems": {
|
||||
"region-console": {
|
||||
|
||||
@@ -47,6 +47,7 @@ class FrontendDigestDecides(unittest.TestCase):
|
||||
)
|
||||
(self.platforms / "digestplat.yml").write_text(yaml.safe_dump({
|
||||
"platform": "DigestPlat",
|
||||
"version": "2.1",
|
||||
"verification_mode": "md5",
|
||||
"base_destination": "bios",
|
||||
"systems": {"psx": {"files": [{
|
||||
|
||||
@@ -1021,7 +1021,7 @@ class AHeldFileTheInstallerCannotFetch(unittest.TestCase):
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
Path("platforms").mkdir()
|
||||
Path("platforms/p.yml").write_text(json.dumps({
|
||||
"platform": "P", "verification_mode": "existence", "cores": ["core"],
|
||||
"platform": "P", "version": "2.1", "verification_mode": "existence", "cores": ["core"],
|
||||
"systems": {"sys": {"files": []}},
|
||||
}))
|
||||
Path("platforms/_registry.yml").write_text(
|
||||
@@ -1096,7 +1096,7 @@ class AStandaloneLayoutBelowTheBiosFolder(unittest.TestCase):
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
Path("platforms").mkdir()
|
||||
Path("platforms/p.yml").write_text(json.dumps({
|
||||
"platform": "P", "verification_mode": "existence",
|
||||
"platform": "P", "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "bios", "cores": ["dsa", "other"],
|
||||
"standalone_cores": ["dsa"], "systems": {"sys": {"files": []}},
|
||||
}))
|
||||
|
||||
@@ -193,6 +193,7 @@ class PackPlacesCoreFilesUnderTheTag(unittest.TestCase):
|
||||
|
||||
platform = {
|
||||
"platform": "MinUI Test",
|
||||
"version": "2025-11-27",
|
||||
"base_destination": "",
|
||||
"verification_mode": "existence",
|
||||
"cores": ["gambatte", "arcadecore", "unlisted"],
|
||||
|
||||
@@ -92,7 +92,7 @@ class _Fixture(unittest.TestCase):
|
||||
for key, profile in PROFILES.items():
|
||||
(self.emulators / f"{key}.yml").write_text(yaml.dump(profile))
|
||||
platform = {
|
||||
"platform": "Handheld", "verification_mode": "existence",
|
||||
"platform": "Handheld", "version": "2.1", "verification_mode": "existence",
|
||||
"cores": ["emu", "doomcore"],
|
||||
"systems": {
|
||||
"console": {"files": [{"name": "firmware.rom", "destination": "firmware.rom"}]},
|
||||
|
||||
@@ -52,6 +52,7 @@ files:
|
||||
|
||||
PLATFORM = {
|
||||
"platform": "Demo",
|
||||
"version": "2.1",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "",
|
||||
"cores": ["demo"],
|
||||
@@ -494,7 +495,7 @@ class RefusedFilesAreNotPresent(unittest.TestCase):
|
||||
}
|
||||
db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
config = {
|
||||
"platform": "Hashy", "verification_mode": "md5", "base_destination": "bios",
|
||||
"platform": "Hashy", "version": "2.1", "verification_mode": "md5", "base_destination": "bios",
|
||||
"systems": {"sys": {"files": [
|
||||
{"name": "good.bin", "destination": "good.bin",
|
||||
"md5": hashlib.md5(b"good.bin").hexdigest()},
|
||||
|
||||
@@ -51,6 +51,7 @@ class RequiredTwins(unittest.TestCase):
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
platform = {
|
||||
"platform": "Twins",
|
||||
"version": "2.1",
|
||||
"verification_mode": "md5",
|
||||
"base_destination": "bios",
|
||||
"cores": [],
|
||||
|
||||
@@ -243,7 +243,7 @@ class _Fixture(unittest.TestCase):
|
||||
"required": False}]}
|
||||
for name in ("testra", "testnosave"):
|
||||
(self.platforms / f"{name}.yml").write_text(yaml.safe_dump({
|
||||
"platform": name, "verification_mode": "existence",
|
||||
"platform": name, "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "system", "cores": ["corex"],
|
||||
"systems": {"test-console": system},
|
||||
}))
|
||||
@@ -399,7 +399,7 @@ class AFolderPerSystem(_Fixture):
|
||||
"required": False}]}
|
||||
for name in ("testbato", "testrb"):
|
||||
(self.platforms / f"{name}.yml").write_text(yaml.safe_dump({
|
||||
"platform": name, "verification_mode": "existence",
|
||||
"platform": name, "version": "2.1", "verification_mode": "existence",
|
||||
"base_destination": "bios", "cores": ["corex", "corey"],
|
||||
"systems": {"test-console": system},
|
||||
# Two systems run CoreX, none runs CoreY.
|
||||
|
||||
@@ -58,6 +58,7 @@ class SlugPlatform(unittest.TestCase):
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
self.config = {
|
||||
"platform": "Slugs",
|
||||
"version": "2.1",
|
||||
"verification_mode": "md5",
|
||||
"base_destination": "bios",
|
||||
"cores": ["dosbox"],
|
||||
@@ -139,7 +140,7 @@ class RegionKeysForEveryClaimant(unittest.TestCase):
|
||||
)
|
||||
common._emulator_profiles_cache.clear()
|
||||
profiles = common.load_emulator_profiles("emulators")
|
||||
config = {"platform": "P", "verification_mode": "existence", "cores": ["one", "two"],
|
||||
config = {"platform": "P", "version": "2.1", "verification_mode": "existence", "cores": ["one", "two"],
|
||||
"systems": {"sys": {"files": []}}}
|
||||
_groups, extra_dests = platform_region_groups(
|
||||
config, config["systems"], "emulators", db, "", profiles
|
||||
@@ -178,7 +179,7 @@ class FlatFirmwareFolder(unittest.TestCase):
|
||||
}
|
||||
db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
config = {
|
||||
"platform": "Flat", "verification_mode": "md5",
|
||||
"platform": "Flat", "version": "2.1", "verification_mode": "md5",
|
||||
"base_destination": "bios", "cores": ["psx"],
|
||||
"systems": {
|
||||
"psx": {"files": [{"name": "scph5501.bin",
|
||||
@@ -300,7 +301,7 @@ class OnePathCountsOnce(unittest.TestCase):
|
||||
common._emulator_profiles_cache.clear()
|
||||
self.addCleanup(common._emulator_profiles_cache.clear)
|
||||
profiles = common.load_emulator_profiles("emulators")
|
||||
config = {"platform": "P", "verification_mode": "existence", "cores": ["one", "two"],
|
||||
config = {"platform": "P", "version": "2.1", "verification_mode": "existence", "cores": ["one", "two"],
|
||||
"systems": {"amiga": {"files": []}}}
|
||||
report = verify_platform(config, db, "emulators", profiles)
|
||||
claims = [u for u in report["undeclared_files"] if u["name"] == "rom.key"]
|
||||
|
||||
Reference in new issue
Block a user