Files
libretro/scripts/generate_truth.py
T
Abdessamad Derraz e9da4d72ed fix: write one truth model per target, not alias
generate_truth named a narrowed model's directory after the target
as typed, so three aliases of one target wrote three copies, and
the pipeline rebuilt the slug by hand to diff it. One function now
names the directory from the canonical target for both.
2026-10-18 02:55:45 +02:00

160 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Generate ground-truth YAML files per platform from emulator profiles.
Usage:
python scripts/generate_truth.py --platform retroarch --output-dir dist/truth
python scripts/generate_truth.py --all --output-dir dist/truth
"""
from __future__ import annotations
import argparse
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
from common import (
list_registered_platforms,
load_database,
load_emulator_profiles,
load_platform_config,
load_target_config,
require_yaml,
truth_target_dir,
write_text_atomic,
yaml_load,
)
from truth import generate_platform_truth
yaml = require_yaml()
DEFAULT_OUTPUT_DIR = "dist/truth"
DEFAULT_PLATFORMS_DIR = "platforms"
DEFAULT_EMULATORS_DIR = "emulators"
DEFAULT_DB_FILE = "database.json"
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate ground-truth YAML from emulator profiles",
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--all", action="store_true", help="all registered platforms")
group.add_argument("--platform", help="single platform name")
parser.add_argument(
"--output-dir",
default=DEFAULT_OUTPUT_DIR,
help="output directory",
)
parser.add_argument(
"--target",
"-t",
default=None,
help="hardware target filter",
)
parser.add_argument(
"--platforms-dir",
default=DEFAULT_PLATFORMS_DIR,
)
parser.add_argument(
"--emulators-dir",
default=DEFAULT_EMULATORS_DIR,
)
parser.add_argument("--db", default=DEFAULT_DB_FILE, help="database.json path")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> None:
args = parse_args(argv)
# Load registry
registry_path = os.path.join(args.platforms_dir, "_registry.yml")
with open(registry_path) as f:
registry = (yaml_load(f) or {}).get("platforms", {})
# Load emulator profiles
profiles = load_emulator_profiles(args.emulators_dir)
# Load database (optional)
db: dict | None = None
if os.path.exists(args.db):
db = load_database(args.db)
# Determine platforms
if args.all:
platforms = list_registered_platforms(
args.platforms_dir,
include_archived=True,
)
else:
platforms = [args.platform]
# A target-narrowed model is a different artifact: written under the
# platform's own name it overwrote the full one, and diff_truth then
# compared a narrowed model against the whole scrape.
output_dir = args.output_dir
if args.target:
output_dir = truth_target_dir(
output_dir, platforms, args.target, args.platforms_dir
)
os.makedirs(output_dir, exist_ok=True)
for name in platforms:
# Resolve target cores
target_cores: set[str] | None = None
if args.target:
try:
target_cores = load_target_config(
name,
args.target,
args.platforms_dir,
)
except FileNotFoundError:
print(f" {name}: no target config, skipped")
continue
# Load platform config (with inheritance) and registry entry
try:
config = load_platform_config(name, args.platforms_dir)
except FileNotFoundError:
print(f" {name}: no platform config, skipped")
continue
registry_entry = registry.get(name, {})
result = generate_platform_truth(
name,
config,
registry_entry,
profiles,
db=db,
target_cores=target_cores,
)
out_path = os.path.join(output_dir, f"{name}.yml")
# Whole or not at all: export_native and diff_truth read this file,
# and a run stopped midway left a truncated model behind.
write_text_atomic(
out_path,
yaml.dump(
result,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
n_systems = len(result.get("systems", {}))
n_files = sum(
len(sys_data.get("files", {}))
for sys_data in result.get("systems", {}).values()
)
print(f" {name}: {n_systems} systems, {n_files} files -> {out_path}")
print("Done.")
if __name__ == "__main__":
from common import run_cli
run_cli(main)