Skip to content

Commit 481dcac

Browse files
committed
[cadence] Run build/test on fork exports and surface per-job checks on the PR
The Cadence Build & Test workflow ran its credentialed build/test jobs only on same-repo PRs. On Meta-exported fork PRs (labeled meta-exported + CLA Signed) the cpu-build and cpu-test jobs were excluded from the fork (pull_request_target) path, and the jobs that did run there did not appear as checks on the PR because pull_request_target check runs attach to the base SHA. Each cadence job now carries an inlined run condition: push/schedule/workflow_dispatch, same-repo pull_request, or a fork pull_request_target labeled CLA Signed + meta-exported. There is no separate gate job; GitHub Actions has no YAML anchors and env is unavailable in job-level if, so the condition is duplicated per job. export-checks-start/export-checks-finish mirror per-job check runs onto the PR head SHA so each cadence job shows up as its own check on the export PR, linking to its logs. Same-repo PRs continue to run via pull_request and report checks natively. Authored with Claude Code.
1 parent 24d8bab commit 481dcac

1 file changed

Lines changed: 127 additions & 36 deletions

File tree

.github/workflows/build-cadence-runner.yml

Lines changed: 127 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,19 @@ concurrency:
1919
cancel-in-progress: true
2020

2121
jobs:
22-
gate:
23-
runs-on: ubuntu-latest
24-
outputs:
25-
run-cadence: ${{ steps.decide.outputs.run }}
26-
steps:
27-
- id: decide
28-
env:
29-
EVENT: ${{ github.event_name }}
30-
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
31-
HAS_CLA: ${{ contains(github.event.pull_request.labels.*.name, 'CLA Signed') }}
32-
HAS_EXPORT: ${{ contains(github.event.pull_request.labels.*.name, 'meta-exported') }}
33-
run: |
34-
run=false
35-
case "${EVENT}" in
36-
push|schedule|workflow_dispatch)
37-
run=true
38-
;;
39-
pull_request)
40-
[ "${IS_FORK}" = "false" ] && run=true
41-
;;
42-
pull_request_target)
43-
if [ "${IS_FORK}" = "true" ] && [ "${HAS_CLA}" = "true" ] && [ "${HAS_EXPORT}" = "true" ]; then
44-
run=true
45-
fi
46-
;;
47-
esac
48-
echo "run=${run}" >> "${GITHUB_OUTPUT}"
49-
22+
# The cadence build/test jobs run when:
23+
# - push / schedule / workflow_dispatch, OR
24+
# - a same-repo pull_request (head is this repo, not a fork), OR
25+
# - a pull_request_target from a fork labeled CLA Signed + meta-exported
26+
# (Meta-exported PRs; the only path that runs credentialed CI on fork code).
27+
# GitHub Actions has no YAML anchors and env is unavailable in job-level if, so
28+
# this condition is duplicated per job below; keep the copies in sync.
5029
cpu-build:
51-
if: github.event_name != 'pull_request_target'
30+
if: >-
31+
github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ||
32+
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
33+
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
34+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported'))
5235
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
5336
permissions:
5437
id-token: write
@@ -58,7 +41,7 @@ jobs:
5841
runner: linux.2xlarge
5942
docker-image: ci-image:executorch-ubuntu-22.04-clang12
6043
submodules: recursive
61-
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
44+
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.sha }}
6245
timeout: 90
6346
upload-artifact: cadence-runner-build
6447
script: |
@@ -75,21 +58,28 @@ jobs:
7558
7659
cpu-test:
7760
needs: cpu-build
78-
if: github.event_name != 'pull_request_target'
61+
if: >-
62+
github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ||
63+
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
64+
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
65+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported'))
7966
permissions:
8067
id-token: write
8168
contents: read
8269
uses: ./.github/workflows/_test_cadence.yml
8370
with:
84-
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
71+
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.sha }}
8572

8673
# Cross-compile cadence_executor_runner for each Cadence Xtensa core, one job
8774
# per backend so they show as separate lines (no matrix grouping). Shared logic
8875
# lives in _xtensa_build.yml. fusion_g3 is omitted until the upstream fusion_g3
8976
# <-> nnlib-FusionG3 API skew is fixed (its runner does not link).
9077
hifi-build:
91-
needs: gate
92-
if: needs.gate.outputs.run-cadence == 'true'
78+
if: >-
79+
github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ||
80+
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
81+
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
82+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported'))
9383
permissions:
9484
id-token: write
9585
contents: read
@@ -99,12 +89,113 @@ jobs:
9989
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.sha }}
10090

10191
vision-build:
102-
needs: gate
103-
if: needs.gate.outputs.run-cadence == 'true'
92+
if: >-
93+
github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ||
94+
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
95+
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
96+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported'))
10497
permissions:
10598
id-token: write
10699
contents: read
107100
uses: ./.github/workflows/_xtensa_build.yml
108101
with:
109102
backend: vision
110103
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.sha }}
104+
105+
# Fork exports run via pull_request_target, whose native check runs attach to
106+
# the base SHA and never surface on the PR. Mirror per-job check runs onto the
107+
# PR head SHA so each cadence job shows up as its own check on the export PR,
108+
# linking to its logs. Same-repo PRs run via pull_request and report natively.
109+
export-checks-start:
110+
if: >-
111+
github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
112+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported')
113+
runs-on: ubuntu-latest
114+
permissions:
115+
checks: write
116+
outputs:
117+
ids: ${{ steps.create.outputs.ids }}
118+
steps:
119+
- id: create
120+
uses: actions/github-script@v7
121+
with:
122+
script: |
123+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
124+
const jobs = ['cpu-build', 'cpu-test', 'hifi-build', 'vision-build'];
125+
const ids = {};
126+
for (const job of jobs) {
127+
const res = await github.rest.checks.create({
128+
owner: context.repo.owner,
129+
repo: context.repo.repo,
130+
name: `cadence (export) / ${job}`,
131+
head_sha: context.payload.pull_request.head.sha,
132+
status: 'in_progress',
133+
details_url: runUrl,
134+
output: {
135+
title: 'Running on the export workflow run',
136+
summary: `Mirrored from the pull_request_target run (native checks attach to the base SHA). See ${runUrl}`,
137+
},
138+
});
139+
ids[job] = res.data.id;
140+
// Persist after each create so a mid-loop failure still propagates
141+
// partial IDs to the finish job (which then updates, not duplicates).
142+
core.setOutput('ids', JSON.stringify(ids));
143+
}
144+
145+
export-checks-finish:
146+
needs: [cpu-build, cpu-test, hifi-build, vision-build, export-checks-start]
147+
if: >-
148+
always() && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository &&
149+
contains(github.event.pull_request.labels.*.name, 'CLA Signed') && contains(github.event.pull_request.labels.*.name, 'meta-exported')
150+
runs-on: ubuntu-latest
151+
permissions:
152+
checks: write
153+
actions: read
154+
steps:
155+
- uses: actions/github-script@v7
156+
env:
157+
NEEDS: ${{ toJSON(needs) }}
158+
IDS: ${{ needs.export-checks-start.outputs.ids }}
159+
with:
160+
script: |
161+
const needs = JSON.parse(process.env.NEEDS);
162+
const ids = JSON.parse(process.env.IDS || '{}');
163+
const owner = context.repo.owner;
164+
const repo = context.repo.repo;
165+
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
166+
const jobs = ['cpu-build', 'cpu-test', 'hifi-build', 'vision-build'];
167+
// Link each check to its inner reusable-workflow job for a direct log
168+
// link; fall back to the run page when a job name is not matchable.
169+
const { data } = await github.rest.actions.listJobsForWorkflowRun({
170+
owner, repo, run_id: context.runId, per_page: 100,
171+
});
172+
const urlFor = (job) => {
173+
const m = data.jobs.find(j => j.name === job || j.name.startsWith(`${job} /`));
174+
return m ? m.html_url : runUrl;
175+
};
176+
// Valid check-run conclusions; anything unexpected fails closed.
177+
const allowed = ['success', 'failure', 'cancelled', 'skipped', 'timed_out'];
178+
for (const job of jobs) {
179+
const result = (needs[job] && needs[job].result) || 'unknown';
180+
const conclusion = allowed.includes(result) ? result : 'failure';
181+
const fields = {
182+
owner,
183+
repo,
184+
status: 'completed',
185+
conclusion,
186+
details_url: urlFor(job),
187+
output: {
188+
title: `${job}: ${result}`,
189+
summary: `Result mirrored from the export workflow run: ${runUrl}`,
190+
},
191+
};
192+
if (ids[job]) {
193+
await github.rest.checks.update({ ...fields, check_run_id: ids[job] });
194+
} else {
195+
await github.rest.checks.create({
196+
...fields,
197+
name: `cadence (export) / ${job}`,
198+
head_sha: context.payload.pull_request.head.sha,
199+
});
200+
}
201+
}

0 commit comments

Comments
 (0)