mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-18 14:52:32 -05:00
feat: add load_target_config and list_available_targets to common.py
Loads per-platform target YAML files from platforms/targets/, resolves aliases and applies add_cores/remove_cores from _overrides.yml. Includes 7 E2E tests covering alias resolution, overrides, and error paths.
This commit is contained in:
@@ -192,6 +192,100 @@ def list_registered_platforms(
|
|||||||
return platforms
|
return platforms
|
||||||
|
|
||||||
|
|
||||||
|
def load_target_config(
|
||||||
|
platform_name: str,
|
||||||
|
target: str,
|
||||||
|
platforms_dir: str = "platforms",
|
||||||
|
) -> set[str]:
|
||||||
|
"""Load target config and return the set of core names for the given target.
|
||||||
|
|
||||||
|
Resolves aliases from _overrides.yml, applies add_cores/remove_cores.
|
||||||
|
Raises ValueError if target is unknown (with list of available targets).
|
||||||
|
Raises FileNotFoundError if no target file exists for the platform.
|
||||||
|
"""
|
||||||
|
targets_dir = os.path.join(platforms_dir, "targets")
|
||||||
|
target_file = os.path.join(targets_dir, f"{platform_name}.yml")
|
||||||
|
if not os.path.exists(target_file):
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"No target config for platform '{platform_name}': {target_file}"
|
||||||
|
)
|
||||||
|
with open(target_file) as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
targets = data.get("targets", {})
|
||||||
|
|
||||||
|
overrides_file = os.path.join(targets_dir, "_overrides.yml")
|
||||||
|
overrides = {}
|
||||||
|
if os.path.exists(overrides_file):
|
||||||
|
with open(overrides_file) as f:
|
||||||
|
all_overrides = yaml.safe_load(f) or {}
|
||||||
|
overrides = all_overrides.get(platform_name, {}).get("targets", {})
|
||||||
|
|
||||||
|
alias_index: dict[str, str] = {}
|
||||||
|
for tname in targets:
|
||||||
|
alias_index[tname] = tname
|
||||||
|
for alias in overrides.get(tname, {}).get("aliases", []):
|
||||||
|
alias_index[alias] = tname
|
||||||
|
|
||||||
|
canonical = alias_index.get(target)
|
||||||
|
if canonical is None:
|
||||||
|
available = sorted(targets.keys())
|
||||||
|
aliases = []
|
||||||
|
for tname, ovr in overrides.items():
|
||||||
|
for a in ovr.get("aliases", []):
|
||||||
|
aliases.append(f"{a} -> {tname}")
|
||||||
|
msg = f"Unknown target '{target}' for platform '{platform_name}'.\n"
|
||||||
|
msg += f"Available targets: {', '.join(available)}"
|
||||||
|
if aliases:
|
||||||
|
msg += f"\nAliases: {', '.join(sorted(aliases))}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
cores = set(str(c) for c in targets[canonical].get("cores", []))
|
||||||
|
|
||||||
|
ovr = overrides.get(canonical, {})
|
||||||
|
for c in ovr.get("add_cores", []):
|
||||||
|
cores.add(str(c))
|
||||||
|
for c in ovr.get("remove_cores", []):
|
||||||
|
cores.discard(str(c))
|
||||||
|
|
||||||
|
return cores
|
||||||
|
|
||||||
|
|
||||||
|
def list_available_targets(
|
||||||
|
platform_name: str,
|
||||||
|
platforms_dir: str = "platforms",
|
||||||
|
) -> list[dict]:
|
||||||
|
"""List available targets for a platform with their aliases.
|
||||||
|
|
||||||
|
Returns list of dicts with keys: name, architecture, core_count, aliases.
|
||||||
|
Returns empty list if no target file exists.
|
||||||
|
"""
|
||||||
|
targets_dir = os.path.join(platforms_dir, "targets")
|
||||||
|
target_file = os.path.join(targets_dir, f"{platform_name}.yml")
|
||||||
|
if not os.path.exists(target_file):
|
||||||
|
return []
|
||||||
|
with open(target_file) as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
overrides_file = os.path.join(targets_dir, "_overrides.yml")
|
||||||
|
overrides = {}
|
||||||
|
if os.path.exists(overrides_file):
|
||||||
|
with open(overrides_file) as f:
|
||||||
|
all_overrides = yaml.safe_load(f) or {}
|
||||||
|
overrides = all_overrides.get(platform_name, {}).get("targets", {})
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for tname, tdata in sorted(data.get("targets", {}).items()):
|
||||||
|
aliases = overrides.get(tname, {}).get("aliases", [])
|
||||||
|
result.append({
|
||||||
|
"name": tname,
|
||||||
|
"architecture": tdata.get("architecture", ""),
|
||||||
|
"core_count": len(tdata.get("cores", [])),
|
||||||
|
"aliases": aliases,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def resolve_local_file(
|
def resolve_local_file(
|
||||||
file_entry: dict,
|
file_entry: dict,
|
||||||
db: dict,
|
db: dict,
|
||||||
|
|||||||
@@ -1175,6 +1175,122 @@ class TestE2E(unittest.TestCase):
|
|||||||
resolved = resolve_platform_cores(config, profiles)
|
resolved = resolve_platform_cores(config, profiles)
|
||||||
self.assertIn("test_alias_core", resolved)
|
self.assertIn("test_alias_core", resolved)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Target config tests (Task 1)
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
|
def _write_target_fixtures(self):
|
||||||
|
"""Create target config fixtures for testing."""
|
||||||
|
targets_dir = os.path.join(self.platforms_dir, "targets")
|
||||||
|
os.makedirs(targets_dir, exist_ok=True)
|
||||||
|
target_config = {
|
||||||
|
"platform": "testplatform",
|
||||||
|
"source": "test",
|
||||||
|
"scraped_at": "2026-01-01T00:00:00Z",
|
||||||
|
"targets": {
|
||||||
|
"target-full": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"cores": ["core_a", "core_b", "core_c"],
|
||||||
|
},
|
||||||
|
"target-minimal": {
|
||||||
|
"architecture": "armv7",
|
||||||
|
"cores": ["core_a"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(os.path.join(targets_dir, "testplatform.yml"), "w") as f:
|
||||||
|
yaml.dump(target_config, f)
|
||||||
|
single_config = {
|
||||||
|
"platform": "singleplatform",
|
||||||
|
"source": "test",
|
||||||
|
"scraped_at": "2026-01-01T00:00:00Z",
|
||||||
|
"targets": {
|
||||||
|
"only-target": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"cores": ["core_a", "core_b"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(os.path.join(targets_dir, "singleplatform.yml"), "w") as f:
|
||||||
|
yaml.dump(single_config, f)
|
||||||
|
overrides = {
|
||||||
|
"testplatform": {
|
||||||
|
"targets": {
|
||||||
|
"target-full": {
|
||||||
|
"aliases": ["full", "pc", "desktop"],
|
||||||
|
"add_cores": ["core_d"],
|
||||||
|
"remove_cores": ["core_c"],
|
||||||
|
},
|
||||||
|
"target-minimal": {
|
||||||
|
"aliases": ["minimal", "arm"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(os.path.join(targets_dir, "_overrides.yml"), "w") as f:
|
||||||
|
yaml.dump(overrides, f)
|
||||||
|
|
||||||
|
def test_load_target_config(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
from common import load_target_config
|
||||||
|
cores = load_target_config("testplatform", "target-minimal", self.platforms_dir)
|
||||||
|
self.assertEqual(cores, {"core_a"})
|
||||||
|
|
||||||
|
def test_target_alias_resolution(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
from common import load_target_config
|
||||||
|
cores = load_target_config("testplatform", "full", self.platforms_dir)
|
||||||
|
self.assertEqual(cores, {"core_a", "core_b", "core_d"})
|
||||||
|
|
||||||
|
def test_target_unknown_error(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
from common import load_target_config
|
||||||
|
with self.assertRaises(ValueError) as ctx:
|
||||||
|
load_target_config("testplatform", "nonexistent", self.platforms_dir)
|
||||||
|
self.assertIn("target-full", str(ctx.exception))
|
||||||
|
self.assertIn("target-minimal", str(ctx.exception))
|
||||||
|
|
||||||
|
def test_target_override_add_remove(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
from common import load_target_config
|
||||||
|
cores = load_target_config("testplatform", "full", self.platforms_dir)
|
||||||
|
self.assertIn("core_d", cores)
|
||||||
|
self.assertNotIn("core_c", cores)
|
||||||
|
self.assertIn("core_a", cores)
|
||||||
|
self.assertIn("core_b", cores)
|
||||||
|
|
||||||
|
def test_target_single_target_noop(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
from common import load_target_config
|
||||||
|
cores = load_target_config("singleplatform", "only-target", self.platforms_dir)
|
||||||
|
self.assertEqual(cores, {"core_a", "core_b"})
|
||||||
|
|
||||||
|
def test_target_inherits(self):
|
||||||
|
self._write_target_fixtures()
|
||||||
|
targets_dir = os.path.join(self.platforms_dir, "targets")
|
||||||
|
child_config = {
|
||||||
|
"platform": "childplatform",
|
||||||
|
"source": "test",
|
||||||
|
"scraped_at": "2026-01-01T00:00:00Z",
|
||||||
|
"targets": {
|
||||||
|
"target-full": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"cores": ["core_a"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(os.path.join(targets_dir, "childplatform.yml"), "w") as f:
|
||||||
|
yaml.dump(child_config, f)
|
||||||
|
from common import load_target_config
|
||||||
|
parent = load_target_config("testplatform", "target-minimal", self.platforms_dir)
|
||||||
|
child = load_target_config("childplatform", "target-full", self.platforms_dir)
|
||||||
|
self.assertEqual(parent, {"core_a"})
|
||||||
|
self.assertEqual(child, {"core_a"})
|
||||||
|
self.assertNotEqual(
|
||||||
|
load_target_config("testplatform", "full", self.platforms_dir),
|
||||||
|
child,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user