From 68f42bf8e231fbcf63f8f859fad0b11ce63574b4 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 27 Oct 2026 05:41:27 +0100 Subject: [PATCH] fix: judge each build's refs in its own tree A profile keyed by build mode compared a part in every repository and kept the best outcome: xenia-edge's libretro tree carries emulator.cc offset, and its lines vouched for standalone refs that had moved 4 lines. A part of an entry with a mode is read in that build's repository, and a part naming none in the first build declared, as _realign_part decides. --- scripts/profile_sync.py | 41 ++++++++++++++++++++++++++++----- tests/test_profile_sync.py | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 2cf73fdbf..e42d99b61 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -131,6 +131,9 @@ class RepoView: head: str origin: str field: str + # The build mode a keyed source names this repository under (ymir, + # xenia-edge): `standalone` or `libretro`, empty for a plain URL. + mode: str = "" HASH_FIELDS = ("sha1", "md5", "crc32", "sha256", "known_hash_adler32") @@ -1149,7 +1152,7 @@ def select_views( if relation and relation[0] == 0 and relation[1] > 0: head = upstream.resolve_head(repo, cache_dir, offline, branch, fresh=True) or head seen.add((repo.host, repo.slug)) - views.append(RepoView(repo, pin, head, origin, field)) + views.append(RepoView(repo, pin, head, origin, field, mode)) if gone and not views: raise upstream.GoneError(f"{profile.get('emulator', '?')}: every declared repository is gone") views = _follow_fork_base(_follow_submodule(views, cache_dir, offline), cache_dir, offline) @@ -1528,6 +1531,7 @@ def build_report( for _, _, url in declared_repositories(profile) if (repo := upstream.parse_repo(url)) is not None ) + modes = {mode for _, mode, _ in declared_repositories(profile) if mode} refs = [] for citation in collect_citations(profile, text): if citation.kind == "ref": @@ -1538,10 +1542,14 @@ def build_report( # The repository a part says its lines are in (`upstream # src/a.c:5`) stays on that part: the same path can be cited # under both roles in one profile. + # An entry of one build mode reads that build's repository when + # the profile keys its source by mode. + mode = str(citation.label or entry.get("mode") or "") + mode = mode if mode in modes else "" parts = [ RefPart( strip_repo_word(p.path, known), p.start, p.end, p.raw, - ref_role(p.path)[0], + ref_role(p.path)[0] or mode, ) for p in split_source_ref(citation.ref) ] @@ -1773,8 +1781,14 @@ def build_report( return 2 return 1 if line.strip() else 0 - # A part that names its repository is looked up there first. - ordered = sorted(views, key=lambda v: _view_role(v) != wanted) if wanted else views + # A part that names its repository is looked up there first. In a + # profile keyed by build mode, a part naming none belongs to the + # first build declared, as _realign_part decides a shared path: the + # libretro tree of xenia-edge carries emulator.cc offset, and its + # better-scored line hid a standalone ref that had moved. + if not wanted and primary.mode: + wanted = primary.mode + ordered = sorted(views, key=lambda v: not _answers(v, wanted)) if wanted else views best = None for candidate in candidates: for sha_of in (lambda v: v.pin, lambda v: v.head): @@ -1789,6 +1803,11 @@ def build_report( continue candidate = target rank = score(found) + if rank and _answers(view, wanted) and view.mode: + # An entry of one build mode is that build's code: + # xenia-edge's standalone xbox.xex refs scored higher + # in the libretro tree, whose same file is offset. + return view, candidate if rank == 3 and not wanted and _view_role(view) == "source": # The port carries a copy of the emulator: when the # original holds the same value on the cited line, @@ -1964,8 +1983,12 @@ def build_report( view for view in views if fetch_from(view.repo, view.pin, part.path) is not None ] - named = [view for view in carrying if _view_role(view) == part.role] - if part.role and named: + # Judged in the build it belongs to: the best outcome across the + # builds of a keyed profile let the libretro tree vouch for a + # standalone ref that had moved (xenia-edge's emulator.cc). + meant = part.role or primary.mode + named = [view for view in carrying if _answers(view, meant)] + if meant and named: carrying = named live = { id(view) for view in carrying @@ -3009,6 +3032,12 @@ def _view_role(view: RepoView) -> str: return "upstream" if view.field == "upstream" else "source" +def _answers(view: RepoView, role: str) -> bool: + """Whether a part written under *role* means this repository: the field + it comes from, or the build mode a keyed source declares it under.""" + return bool(role) and role in (_view_role(view), view.mode) + + ATTRIBUTED_STATUSES = ("ANCHORED", "SHIFTED", "RENAMED", "MOVED", "CHANGED") diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 140e6df6e..1c78c1f94 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -2845,6 +2845,53 @@ class TestWriteDryRun(unittest.TestCase): self.assertEqual(self.path.read_text(), before) +class AKeyedProfileJudgesEachBuildInItsOwnTree(TestBuildReport): + """xenia-edge's standalone emulator.cc block moved down 4 lines while + the libretro tree, at HEAD, carried the same path offset: the best + outcome across repositories reported ANCHORED and the move went unseen.""" + + def _keyed(self, mode=None): + entry = {"name": "f.bin", "source_ref": "a.c:2"} + if mode: + entry["mode"] = mode + return { + "emulator": "Test", + "source": {"standalone": "https://github.com/o/edge", + "libretro": "https://github.com/o/port"}, + "upstream": "https://github.com/o/edge", + "source_commit": {"standalone": "spin", "libretro": "lpin"}, + "files": [entry], + } + + def _two_builds(self): + trees = { + ("o/edge", "spin", "a.c"): ["x", "f.bin cited"], + ("o/edge", "shead", "a.c"): ["pad", "pad", "x", "f.bin cited"], + ("o/port", "lpin", "a.c"): ["y", "f.bin cited"], + } + heads = {"o/edge": "shead", "o/port": "lpin"} + profile_sync.upstream.fetch_file = ( + lambda repo, sha, path, cache_dir, offline=False: + trees.get((repo.slug, sha, path)) + ) + profile_sync.upstream.resolve_head = ( + lambda repo, cache_dir, offline=False, branch=None, fresh=False: + heads[repo.slug] + ) + + def _status(self, profile): + self._two_builds() + report = build_report("test", profile, self.dir) + return report.entries[0].parts[0].status + + def test_a_part_naming_no_build_is_the_first_builds(self): + self.assertEqual(self._status(self._keyed()), "SHIFTED") + + def test_an_entry_of_one_mode_is_read_in_that_build(self): + self.assertEqual(self._status(self._keyed("standalone")), "SHIFTED") + self.assertNotEqual(self._status(self._keyed("libretro")), "SHIFTED") + + class TestDeliberatelyFrozenPin(TestBuildReport): """A pin held on purpose is judged against itself, like a frozen tag.