Skip to content

Commit 7cbd46a

Browse files
Merge pull request #11381 from sensei-hacker/feature/pr-test-builds
CI: publish PR test builds to iNavFlight/pr-test-builds
2 parents cfdcbeb + 37e02bd commit 7cbd46a

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ jobs:
107107
with:
108108
name: targets
109109
path: targets.txt
110+
- name: Save PR number
111+
if: github.event_name == 'pull_request'
112+
run: echo "${{ github.event.pull_request.number }}" > pr_number.txt
113+
- name: Upload PR number
114+
if: github.event_name == 'pull_request'
115+
uses: actions/upload-artifact@v4
116+
with:
117+
name: pr-number
118+
path: pr_number.txt
119+
retention-days: 1
110120

111121
build-SITL-Linux-arm64:
112122
runs-on: ubuntu-22.04-arm
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: PR Test Builds
2+
3+
# Runs after "Build firmware" completes. Uses workflow_run (rather than
4+
# pull_request directly) so that secrets are available even for PRs from forks.
5+
#
6+
# Requires a repository secret PR_BUILDS_TOKEN with Contents: write access
7+
# to iNavFlight/pr-test-builds (fine-grained PAT or classic PAT with repo scope).
8+
on:
9+
workflow_run:
10+
workflows: ["Build firmware"]
11+
types: [completed]
12+
13+
jobs:
14+
publish:
15+
runs-on: ubuntu-latest
16+
# Only act on pull_request-triggered runs that succeeded.
17+
if: >
18+
github.event.workflow_run.event == 'pull_request' &&
19+
github.event.workflow_run.conclusion == 'success'
20+
# Prevent concurrent runs for the same PR branch racing on the
21+
# release delete/create cycle.
22+
concurrency:
23+
group: pr-test-build-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
24+
cancel-in-progress: true
25+
permissions:
26+
actions: read # to download artifacts from the triggering workflow run
27+
issues: write # github.rest.issues.* endpoints used to post PR comments
28+
pull-requests: write # to post the PR comment
29+
30+
steps:
31+
- name: Download PR number
32+
uses: actions/download-artifact@v4
33+
with:
34+
name: pr-number
35+
run-id: ${{ github.event.workflow_run.id }}
36+
github-token: ${{ secrets.GITHUB_TOKEN }}
37+
38+
- name: Read PR number
39+
id: pr
40+
run: |
41+
PR_NUM=$(tr -dc '0-9' < pr_number.txt)
42+
if [ -z "$PR_NUM" ]; then
43+
echo "::error::Invalid PR number in artifact"
44+
exit 1
45+
fi
46+
echo "number=${PR_NUM}" >> $GITHUB_OUTPUT
47+
48+
- name: Download firmware artifacts
49+
uses: actions/download-artifact@v4
50+
with:
51+
pattern: matrix-inav-*
52+
merge-multiple: true
53+
path: hexes
54+
run-id: ${{ github.event.workflow_run.id }}
55+
github-token: ${{ secrets.GITHUB_TOKEN }}
56+
57+
- name: Get build info
58+
id: info
59+
run: |
60+
COUNT=$(find hexes -name '*.hex' -type f | wc -l)
61+
if [ "$COUNT" -eq 0 ]; then
62+
echo "::error::No .hex files found in downloaded artifacts"
63+
exit 1
64+
fi
65+
echo "count=${COUNT}" >> $GITHUB_OUTPUT
66+
echo "short_sha=$(echo '${{ github.event.workflow_run.head_sha }}' | cut -c1-7)" >> $GITHUB_OUTPUT
67+
68+
# Delete the previous release for this PR (if any) so assets are replaced
69+
# cleanly on each new commit. --cleanup-tag removes the old tag so it is
70+
# recreated fresh pointing to the new commit.
71+
- name: Delete existing PR release
72+
env:
73+
GH_TOKEN: ${{ secrets.PR_BUILDS_TOKEN }}
74+
run: |
75+
gh release delete "pr-${{ steps.pr.outputs.number }}" \
76+
--repo iNavFlight/pr-test-builds --cleanup-tag --yes 2>/dev/null || true
77+
78+
- name: Create PR release
79+
env:
80+
GH_TOKEN: ${{ secrets.PR_BUILDS_TOKEN }}
81+
run: |
82+
PR_NUMBER="${{ steps.pr.outputs.number }}"
83+
SHORT_SHA="${{ steps.info.outputs.short_sha }}"
84+
COUNT="${{ steps.info.outputs.count }}"
85+
PR_URL="https://github.com/${{ github.repository }}/pull/${PR_NUMBER}"
86+
87+
NOTES="Test build for [PR #${PR_NUMBER}](${PR_URL}) — commit \`${SHORT_SHA}\`
88+
89+
**${COUNT} targets built.** Find your board's \`.hex\` file by name (e.g. \`MATEKF405SE.hex\`).
90+
91+
> Development build for testing only. Use Full Chip Erase when flashing."
92+
93+
cd hexes
94+
gh release create "pr-${PR_NUMBER}" *.hex \
95+
--repo iNavFlight/pr-test-builds \
96+
--prerelease \
97+
--target "${{ github.event.workflow_run.head_sha }}" \
98+
--title "PR #${PR_NUMBER} (${SHORT_SHA})" \
99+
--notes "${NOTES}"
100+
101+
- name: Post or update PR comment
102+
uses: actions/github-script@v7
103+
env:
104+
PR_NUMBER: ${{ steps.pr.outputs.number }}
105+
SHORT_SHA: ${{ steps.info.outputs.short_sha }}
106+
HEX_COUNT: ${{ steps.info.outputs.count }}
107+
with:
108+
script: |
109+
const prNumber = parseInt(process.env.PR_NUMBER, 10);
110+
if (isNaN(prNumber)) throw new Error(`Invalid PR number: ${process.env.PR_NUMBER}`);
111+
const shortSha = process.env.SHORT_SHA;
112+
const count = process.env.HEX_COUNT;
113+
const releaseUrl = `https://github.com/iNavFlight/pr-test-builds/releases/tag/pr-${prNumber}`;
114+
115+
const body = [
116+
'<!-- pr-test-build -->',
117+
'**Test firmware build ready** — commit `' + shortSha + '`',
118+
'',
119+
`[Download firmware for PR #${prNumber}](${releaseUrl})`,
120+
'',
121+
`${count} targets built. Find your board's \`.hex\` file by name on that page ` +
122+
'(e.g. `MATEKF405SE.hex`). Files are individually downloadable — no GitHub login required.',
123+
'',
124+
'> Development build for testing only. Use Full Chip Erase when flashing.',
125+
].join('\n');
126+
127+
const comments = await github.paginate(
128+
github.rest.issues.listComments,
129+
{
130+
owner: context.repo.owner,
131+
repo: context.repo.repo,
132+
issue_number: prNumber,
133+
}
134+
);
135+
136+
const existing = comments.find(c =>
137+
c.user.type === 'Bot' && c.body.includes('<!-- pr-test-build -->')
138+
);
139+
140+
if (existing) {
141+
await github.rest.issues.updateComment({
142+
owner: context.repo.owner,
143+
repo: context.repo.repo,
144+
comment_id: existing.id,
145+
body,
146+
});
147+
} else {
148+
await github.rest.issues.createComment({
149+
owner: context.repo.owner,
150+
repo: context.repo.repo,
151+
issue_number: prNumber,
152+
body,
153+
});
154+
}

0 commit comments

Comments
 (0)