From 07c82c885f8b4d22d6348135b657aa61f3bf42f6 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:48:16 +0100 Subject: [PATCH] refactor: read the fbneo version from version.h FBNeo bumps src/burn/version.h without tagging: the scraper wrote the last tag, v1.0.0.02, while the source said 1.0.0.03. It now reads the header the way the libretro core prints it. The merge no longer writes core_version: the hashes come from finalburnneo/FBNeo's tip, which the libretro/FBNeo pin does not name, so the version stays the one version_basis reads at that pin. A MAME release profile is written with its version_basis. --- scripts/scraper/_hash_merge.py | 16 +++-- scripts/scraper/fbneo_hash_scraper.py | 96 ++++++++------------------- scripts/scraper/mame_hash_scraper.py | 7 ++ tests/test_e2e.py | 3 +- tests/test_hash_merge.py | 9 ++- 5 files changed, 51 insertions(+), 80 deletions(-) diff --git a/scripts/scraper/_hash_merge.py b/scripts/scraper/_hash_merge.py index 1642357f5..4fe3cc93d 100644 --- a/scripts/scraper/_hash_merge.py +++ b/scripts/scraper/_hash_merge.py @@ -158,7 +158,12 @@ def merge_fbneo_profile( profile = _load_yaml(profile_path) hashes = _load_json(hashes_path) - profile["core_version"] = hashes.get("version", profile.get("core_version")) + # core_version stays as written: it is the version of the code at the + # profile's own pin, read through version_basis (profile_sync + # --set-version). The hashes come from finalburnneo/FBNeo's tip, a + # revision the libretro/FBNeo pin does not name: writing its version + # left fbneo, fbneo_cps12 and fbneo_neogeo on a release their pin + # never was. files = profile.get("files", []) archive_files, non_archive = _split_files(files, lambda f: "archive" in f) @@ -1060,17 +1065,16 @@ def _backup_and_write_fbneo(path: str, data: dict, hashes: dict) -> None: """Write merged FBNeo profile using text-based patching. FBNeo profiles have individual ROM entries with archive: field. - Patches core_version, the content values of existing entries that - changed, and appends new ROM entries. Advancing core_version alone left - a profile claiming a release whose crc32 it did not carry. Comments and - curated source_refs stay as written. + Patches the content values of existing entries that changed and + appends new ROM entries. core_version is the pin's, never written here. + Comments and curated source_refs stay as written. """ p = Path(path) backup = p.with_suffix(PROFILE_BACKUP_SUFFIX) shutil.copy2(p, backup) original = p.read_text(encoding="utf-8") - patched = _patch_core_version(original, data.get("core_version", "")) + patched = original # Identify new ROM entries by comparing parsed data keys, not text search existing_data = yaml_load(original) or {} diff --git a/scripts/scraper/fbneo_hash_scraper.py b/scripts/scraper/fbneo_hash_scraper.py index b641be10c..17ac95a43 100644 --- a/scripts/scraper/fbneo_hash_scraper.py +++ b/scripts/scraper/fbneo_hash_scraper.py @@ -11,6 +11,7 @@ import argparse import json import logging import os +import re import shutil import subprocess import sys @@ -20,7 +21,7 @@ from typing import Any import yaml -from common import github_token, source_yaml_paths, write_text_atomic, yaml_load +from common import source_yaml_paths, write_text_atomic, yaml_load from scripts.scraper._hash_merge import compute_diff, merge_fbneo_profile from scripts.scraper.fbneo_parser import parse_fbneo_source_tree @@ -51,7 +52,7 @@ def _is_cache_fresh() -> bool: def _sparse_clone() -> None: - """Sparse clone FBNeo repo, checking out only src/burn/drv.""" + """Sparse clone FBNeo repo: src/burn/drv, and in cone mode src/burn/version.h.""" if CLONE_DIR.exists(): shutil.rmtree(CLONE_DIR) @@ -74,7 +75,7 @@ def _sparse_clone() -> None: ) subprocess.run( - ["git", "sparse-checkout", "set", "src/burn/drv", "src/burner/resource.h"], + ["git", "sparse-checkout", "set", "src/burn/drv"], cwd=CLONE_DIR, check=True, capture_output=True, @@ -82,31 +83,28 @@ def _sparse_clone() -> None: ) +# src/burn/version.h, as the libretro core prints it: "v%x.%x.%x.%02x" of +# the BCD BURN_VERSION, so VER_ALPHA 3 reads 1.0.0.03. FBNeo bumps this file +# without tagging: the last tag (v1.0.0.02) was 9 337 commits behind it. +_VERSION_H = re.compile( + r"#define\s+VER_MAJOR\s+(\d+)\s+#define\s+VER_MINOR\s+(\d+)\s+" + r"#define\s+VER_BETA\s+(\d+)\s+#define\s+VER_ALPHA\s+(\d+)" +) + + +def version_from_header(text: str) -> str: + """The version src/burn/version.h states.""" + found = _VERSION_H.search(text) + if not found: + raise RuntimeError("src/burn/version.h states no VER_MAJOR..VER_ALPHA") + major, minor, beta, alpha = (int(group) for group in found.groups()) + return f"v{major}.{minor}.{beta}.{alpha:02d}" + + def _extract_version() -> tuple[str, str]: - """Extract version tag and commit SHA from the cloned repo. - - Returns (version, commit_sha). Falls back to resource.h if no tag. - """ - result = subprocess.run( - ["git", "describe", "--tags", "--abbrev=0"], - cwd=CLONE_DIR, - capture_output=True, - text=True, - ) - - # Prefer real version tags over pseudo-tags like "latest" - version = "unknown" - if result.returncode == 0: - tag = result.stdout.strip() - if tag and tag != "latest": - version = tag - # Fallback: resource.h - if version == "unknown": - version = _version_from_resource_h() - # Last resort: use GitHub API for latest real release tag - if version == "unknown": - version = _version_from_api() - + """The version the cloned source states, and its commit.""" + header = CLONE_DIR / "src" / "burn" / "version.h" + version = version_from_header(header.read_text(encoding="utf-8", errors="replace")) sha_result = subprocess.run( ["git", "rev-parse", "HEAD"], cwd=CLONE_DIR, @@ -114,49 +112,7 @@ def _extract_version() -> tuple[str, str]: text=True, check=True, ) - commit = sha_result.stdout.strip() - - return version, commit - - -def _version_from_api() -> str: - """The latest real release tag, or an error: "unknown" was written as - core_version and cached for a day.""" - import json as json_mod - import urllib.error - import urllib.request - - headers = {"User-Agent": "retrobios-scraper/1.0"} - token = github_token() - if token: - headers["Authorization"] = f"Bearer {token}" - req = urllib.request.Request( - "https://api.github.com/repos/finalburnneo/FBNeo/tags?per_page=10", headers=headers - ) - try: - with urllib.request.urlopen(req, timeout=10) as resp: - tags = json_mod.loads(resp.read()) - except (urllib.error.URLError, OSError, ValueError) as exc: - raise RuntimeError(f"cannot determine the FBNeo version: {exc}") from exc - for tag in tags: - if tag["name"] != "latest" and tag["name"].startswith("v"): - return tag["name"] - raise RuntimeError("cannot determine the FBNeo version: no tag, resource.h or release") - - -def _version_from_resource_h() -> str: - """Fallback: parse VER_FULL_VERSION_STR from resource.h.""" - resource_h = CLONE_DIR / "src" / "burner" / "resource.h" - if not resource_h.exists(): - return "unknown" - - text = resource_h.read_text(encoding="utf-8", errors="replace") - for line in text.splitlines(): - if "VER_FULL_VERSION_STR" in line: - parts = line.split('"') - if len(parts) >= 2: - return parts[1] - return "unknown" + return version, sha_result.stdout.strip() def _cleanup() -> None: diff --git a/scripts/scraper/mame_hash_scraper.py b/scripts/scraper/mame_hash_scraper.py index 0d2e8411d..bbdc38d08 100644 --- a/scripts/scraper/mame_hash_scraper.py +++ b/scripts/scraper/mame_hash_scraper.py @@ -467,6 +467,13 @@ def release_profile( "source_commit": source_commit, "upstream_commit": upstream_commit, "core_version": version, + # Where profile_sync reads it again: the makefile of the libretro/mame + # tree the platform builds, at source_commit. + "version_basis": { + "kind": "file", + "path": "makefile", + "pattern": '#define BARE_BUILD_VERSION "([^"]+)"', + }, "display_name": f"Arcade (MAME {version})", "cores": cores, "built_by": built_by, diff --git a/tests/test_e2e.py b/tests/test_e2e.py index 4a92f198a..4b701ca7b 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -4905,7 +4905,8 @@ struct BurnDriver BurnDrvneogeo = { rom = next(f for f in result["files"] if f["name"] == "sp-s2.sp1") self.assertEqual(rom["crc32"], "newcrc") self.assertEqual(rom["system"], "snk-neogeo-mvs") - self.assertEqual(result["core_version"], "v1.0.0.03") + # The version is the pin's, read through version_basis. + self.assertEqual(result["core_version"], "v1.0.0.02") def _load_config(self, platform_name: str) -> dict: diff --git a/tests/test_hash_merge.py b/tests/test_hash_merge.py index 3b28b37ce..079dfe4e5 100644 --- a/tests/test_hash_merge.py +++ b/tests/test_hash_merge.py @@ -592,7 +592,10 @@ class TestFbneoMerge(unittest.TestCase): self.assertEqual(len(archive_files), 1) self.assertNotIn("_upstream_removed", archive_files[0]) - def test_merge_updates_core_version(self) -> None: + def test_merge_leaves_core_version_to_the_pin(self) -> None: + # The hashes are read at finalburnneo/FBNeo's tip, which the + # profile's libretro/FBNeo pin does not name: the version stays the + # one version_basis reads at that pin. with tempfile.TemporaryDirectory() as td: p = Path(td) profile_path = _write_yaml(p / "fbneo.yml", _make_fbneo_profile()) @@ -600,7 +603,7 @@ class TestFbneoMerge(unittest.TestCase): result = merge_fbneo_profile(profile_path, hashes_path) - self.assertEqual(result["core_version"], "v1.0.0.03") + self.assertEqual(result["core_version"], "v1.0.0.02") class TestDiff(unittest.TestCase): @@ -849,7 +852,7 @@ class WritersPutTheValuesOnDisk(unittest.TestCase): merge_fbneo_profile(str(profile), hashes, write=True, add_new=False) written = yaml.safe_load(profile.read_text(encoding="utf-8")) entry = written["files"][0] - self.assertEqual(written["core_version"], "v1.0.0.03") + self.assertEqual(written["core_version"], "v1.0.0.02") self.assertEqual(entry["crc32"], "9036d879") self.assertEqual(entry["note"], "MVS Asia/Europe ver. 5") self.assertEqual(entry["source_ref"], "src/burn/drv/neogeo/d_neogeo.cpp:1625")