From eee902ac80eae78b7b671bc24ed15d35db5dbf5f Mon Sep 17 00:00:00 2001 From: Ahmad Mujahid Date: Sun, 25 Oct 2026 14:52:36 +0400 Subject: [PATCH] ci(build): run the relinker jobs for relinker-only build inputs outside core/relinker The relinker eligibility list was written on 10-12 and missed inputs added to the relinker-only build since then: the nid sources that relinker_core and two relinker tests compile, the libkernel headers that ParamJsonParser.cpp includes, the pad command file test, the recompiler test targets and cache source tests, and the PR template and pr-overlap.yml that tools tests read. A pull request that changed only these got no automatic build, and a push to main skipped the macOS relinker hosts for them. The recompiler is listed as a directory because shader_cache_runtime_sources copies all of core/shader/recompiler and hashes every file its CMakeLists globs, so an edit to any of them can change that test. An edit to the relinker-asan job alone also skipped both relinker jobs, because only edits to the relinker job counted. --- .github/workflows/build.yml | 8 +++-- tools/tests/test_build_eligibility.py | 43 ++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7813e6b53e..f16706d74e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -126,9 +126,13 @@ jobs: paths.some((path) => prefixes.some((prefix) => prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix)); const edits = (job) => ['*', '', 'eligibility', job].some((name) => workflowJobs.has(name)); return { - relinker: edits('relinker') || touches( + relinker: edits('relinker') || edits('relinker-asan') || touches( 'core/relinker/', libc, 'core/libs/prx/libkernel/AppMetadata/', 'core/libs/tests/ParamJsonParser.cpp', - 'core/shader/recompiler/tests/CacheFileReadTests.cpp', 'core/shader/recompiler/ShaderCacheDirectory.hpp', + 'core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp', 'core/libs/prx/libkernel/KernelErrors.hpp', + 'core/libs/nid/src/NidCompute.cpp', 'core/libs/nid/src/Sha1.cpp', + 'core/libs/nid/include/nid/NidCompute.hpp', 'core/libs/nid/include/nid/Sha1.hpp', + 'core/libs/prx/libScePad/tests/InputCommandFile.cpp', 'core/libs/prx/libScePad/include/InputCommandFile.hpp', + 'core/shader/recompiler/', '.github/pull_request_template.md', '.github/workflows/pr-overlap.yml', 'tools/', 'cmake/', 'CMakeLists.txt' ), registerContext: edits('register-context') || diff --git a/tools/tests/test_build_eligibility.py b/tools/tests/test_build_eligibility.py index 9bc78b0bcb..2943298190 100644 --- a/tools/tests/test_build_eligibility.py +++ b/tools/tests/test_build_eligibility.py @@ -35,7 +35,8 @@ async function run(scenario) { return { data: scenario.commits }; }, getBranch: async () => ({ data: { commit: { sha: 'main' } } }), - getCollaboratorPermissionLevel: async () => ({ data: { permission: 'write' } }) + getCollaboratorPermissionLevel: async () => ({ data: { permission: 'write' } }), + getContent: async () => ({ data: { content: Buffer.from(scenario.workflow ?? '').toString('base64') } }) }, search: { issuesAndPullRequests: async (params) => { @@ -47,7 +48,7 @@ async function run(scenario) { } } }, - paginate: async () => scenario.files.map((filename) => ({ filename })) + paginate: async () => scenario.files.map((file) => typeof file === 'string' ? { filename: file } : file) }; const context = { eventName: scenario.manual ? 'workflow_dispatch' : 'pull_request', @@ -116,9 +117,9 @@ class RelinkerEligibilityTests(unittest.TestCase): cls.process.wait() cls.temp.cleanup() - def run_gate(self, commits, search, files=("core/relinker/main.cpp",), manual=False): + def run_gate(self, commits, search, files=("core/relinker/main.cpp",), manual=False, workflow=None): self.process.stdin.write(json.dumps({ - "commits": commits, "search": search, "files": list(files), "manual": manual, + "commits": commits, "search": search, "files": list(files), "manual": manual, "workflow": workflow, }) + "\n") self.process.stdin.flush() response = queue.Queue(maxsize=1) @@ -174,6 +175,40 @@ class RelinkerEligibilityTests(unittest.TestCase): self.assertIs(result["outputs"]["write-watch"], True) self.assertEqual(result["calls"], []) + def test_relinker_build_inputs_outside_the_relinker_run_the_relinker(self): + for path in ("core/libs/nid/src/NidCompute.cpp", "core/libs/nid/src/Sha1.cpp", + "core/libs/nid/include/nid/NidCompute.hpp", "core/libs/nid/include/nid/Sha1.hpp", + "core/libs/prx/libkernel/KernelErrors.hpp", "core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp", + "core/libs/prx/libScePad/tests/InputCommandFile.cpp", "core/libs/prx/libScePad/include/InputCommandFile.hpp", + "core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp", + "core/shader/recompiler/ShaderCacheVersion.cmake", ".github/pull_request_template.md", + ".github/workflows/pr-overlap.yml"): + with self.subTest(path=path): + self.assertIs(self.run_gate([{"sha": "a"}], None, files=(path,))["outputs"]["relinker"], True) + + def test_files_beside_relinker_build_inputs_skip_the_relinker(self): + for path in ("core/libs/nid/src/ElfNidPatcher.cpp", "core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp", + "core/libs/prx/libScePad/include/Pad.hpp", ".github/workflows/labels.yml"): + with self.subTest(path=path): + result = self.run_gate([{"sha": "a"}], None, files=(path,)) + self.assertIs(result["outputs"]["relinker"], False) + self.assertEqual(result["calls"], []) + + def test_workflow_edit_runs_only_the_jobs_it_touches(self): + text = WORKFLOW.read_text(encoding="utf-8") + lines = text.splitlines() + for job, relinker, register_context in (("relinker-asan", True, False), ("register-context", False, True)): + with self.subTest(job=job): + line = next(index for index in range(lines.index(f" {job}:"), len(lines)) + if lines[index].strip().startswith("timeout-minutes:")) + 1 + patch = f"@@ -{line},1 +{line},1 @@\n-{lines[line - 1]}\n+{lines[line - 1]}" + result = self.run_gate([{"sha": "a"}], None, workflow=text, + files=({"filename": ".github/workflows/build.yml", "patch": patch},)) + self.assertIs(result["outputs"]["relinker"], relinker) + self.assertIs(result["outputs"]["register-context"], register_context) + self.assertIs(result["outputs"]["write-watch"], False) + self.assertEqual(result["warnings"], []) + def test_manual_run_checks_no_author(self): result = self.run_gate([], None, manual=True) self.assertIs(result["outputs"]["relinker"], True)