blob: dfaab8b7afb565875edf2f60ea175bb4b3a222d2 [file]
name: PR Comment
on:
workflow_run:
workflows: ["Build"]
types:
- completed
jobs:
# Resolve the PR number from trusted workflow_run metadata, NOT from build artifacts: a forked PR
# controls its own Build run and could plant any number, which the privileged jobs below would
# then post to. Same-repo PRs populate workflow_run.pull_requests; for forks it is empty, so look
# the PR up by the trusted head SHA.
pr_number:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
number: ${{ steps.resolve.outputs.number }}
steps:
- name: Resolve PR number
id: resolve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
PRS_JSON: ${{ toJSON(github.event.workflow_run.pull_requests) }}
run: |
# Every lookup is best-effort: on any miss the number stays empty and the comment jobs
# below simply skip (never a failed check).
# Same-repo PRs: workflow_run.pull_requests is populated.
num=$(printf '%s' "$PRS_JSON" | jq -r '.[0].number // empty')
# Fork PRs: pull_requests is empty. Find the open PR by its trusted head ref and confirm
# its head SHA matches the built commit.
if [ -z "$num" ] && [ -n "$HEAD_BRANCH" ] && [ -n "$HEAD_REPO" ]; then
num=$(gh api --method GET "repos/$REPO/pulls" \
-f state=open -f head="${HEAD_REPO%%/*}:$HEAD_BRANCH" \
--jq '[.[] | select(.head.sha == env.HEAD_SHA)][0].number // empty' 2>/dev/null || true)
fi
echo "number=$num" >> "$GITHUB_OUTPUT"
# ---------------------------------------
# Combine the rigs' HIL reports into one sticky PR comment (one table per rig).
# Runs here (workflow_run / base-repo context) rather than in build.yml so it also works on
# forked PRs, whose build-side GITHUB_TOKEN is read-only and cannot post comments. Posts even
# on build/HIL failure (when the report matters most); skips only on cancellation.
# ---------------------------------------
hil-comment:
needs: pr_number
if: >
github.event.workflow_run.conclusion != 'cancelled' &&
needs.pr_number.outputs.number != ''
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
steps:
# the SystemView section's scripts: the default branch's, never the PR's; its records are data
- name: Checkout default branch scripts
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Download HIL reports
uses: actions/download-artifact@v5
with:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
pattern: hil-report-*
path: hil-reports
continue-on-error: true
- name: Download SystemView captures
id: sv-download
uses: actions/download-artifact@v5
with:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
pattern: sysview-*
path: ${{ runner.temp }}/sysview/pr
continue-on-error: true
timeout-minutes: 5
# a run a newer push or attempt supersedes would only discard the SystemView work below; "Skip a superseded
# run" checks again before posting
- name: Check the run is still the PR's latest
id: early-current
env:
PR: ${{ needs.pr_number.outputs.number }}
RUN_ID: ${{ github.event.workflow_run.id }}
ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
# outcome, not conclusion: continue-on-error turns a failed download's conclusion into success
PR_DOWNLOAD: ${{ steps.sv-download.outcome }}
run: |
SV="$RUNNER_TEMP/sysview"
mkdir -p "$SV"
# find, not a glob: a download of one artifact extracts into the path itself, of several each into its own
# subdirectory
if [ -n "$(find "$SV/pr" -name 'sysview-*.json' -print -quit 2>/dev/null)" ]; then
# until a later step renders the report, or names its own failure
echo '_SystemView report unavailable: a step did not finish (see the job log)._' > "$SV/notice.md"
elif [ "$PR_DOWNLOAD" != success ]; then
echo '_SystemView report unavailable: the captures did not download (see the job log)._' > "$SV/notice.md"
# no record at all: silent unless a HIL report shows a board tested whose capture should have recorded
elif ! python3 test/hil/sysview_ci.py unrecorded hil-reports "$SV/pr" > "$SV/notice.md"; then
echo '_SystemView report unavailable: checking the HIL reports for captures failed (see the job log)._' \
> "$SV/notice.md"
fi
if python3 .github/scripts/code_size_ci.py is-current --repo "$REPO" --pr "$PR" \
--run-id "$RUN_ID" --attempt "$ATTEMPT" --head-sha "$HEAD_SHA"; then
echo "current=true" >> "$GITHUB_OUTPUT"
fi
# Every SystemView step is best effort and bounded: a failure or a step its timeout cut leaves a notice in the
# section, never the HIL comment unposted.
- name: Find the SystemView baseline
id: sv-baseline
if: steps.early-current.outputs.current == 'true'
continue-on-error: true
timeout-minutes: 5
env:
PR: ${{ needs.pr_number.outputs.number }}
run: |
SV="$RUNNER_TEMP/sysview"
mkdir -p "$SV/pr"
if [ -z "$(find "$SV/pr" -name 'sysview-*.json' -print -quit)" ]; then exit 0; fi
python3 test/hil/sysview_ci.py baseline --repo "$REPO" --pr "$PR" --current "$SV/pr" \
--info "$SV/baseline.json"
echo "run_id=$(jq -r '.run_id // empty' "$SV/baseline.json")" >> "$GITHUB_OUTPUT"
echo "ready=true" >> "$GITHUB_OUTPUT"
- name: Download SystemView baseline captures
id: sv-base-download
if: steps.sv-baseline.outputs.run_id != ''
continue-on-error: true
timeout-minutes: 5
uses: actions/download-artifact@v5
with:
run-id: ${{ steps.sv-baseline.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
pattern: sysview-*
path: ${{ runner.temp }}/sysview/base
- name: Render the SystemView report
if: steps.sv-baseline.outputs.ready == 'true'
continue-on-error: true
timeout-minutes: 15
env:
BASELINE_RUN_ID: ${{ steps.sv-baseline.outputs.run_id }}
# outcome, not conclusion: continue-on-error turns a failed download's conclusion into success
PR_DOWNLOAD: ${{ steps.sv-download.outcome }}
BASE_DOWNLOAD: ${{ steps.sv-base-download.outcome }}
run: |
SV="$RUNNER_TEMP/sysview"
mkdir -p "$SV/base"
# a failed or partial baseline download would read its missing boards as "new": report the PR alone
if [ -n "$BASELINE_RUN_ID" ] && { [ "$BASE_DOWNLOAD" != success ] ||
[ -z "$(find "$SV/base" -name 'sysview-*.json' -print -quit)" ]; }; then
jq -n --arg run "$BASELINE_RUN_ID" '{sha: null, url: null, exact: false, run_id: null,
note: "the captures of baseline run \($run) did not download (see the job log)"}' > "$SV/baseline.json"
rm -rf "$SV/base" && mkdir "$SV/base"
fi
# the default branch's decoder (lib/agentrc at its pin); a failed fetch leaves every record undecoded
python3 tools/get_deps.py lib/agentrc || true
python3 test/hil/sysview_ci.py decode "$SV/pr" --budget-s 300 || true
python3 test/hil/sysview_ci.py decode "$SV/base" --budget-s 300 || true
if python3 test/hil/sysview_ci.py report "$SV/base" "$SV/pr" --baseline-info "$SV/baseline.json" \
--hil-reports hil-reports -o "$SV/report.md"; then
rm -f "$SV/notice.md"
# a failed download can leave some of the run's sysview-* artifacts extracted and not others
if [ "$PR_DOWNLOAD" != success ] && [ -s "$SV/report.md" ]; then
printf '\n%s\n' '_Some SystemView captures did not download (see the job log): boards may be missing._' \
>> "$SV/report.md"
fi
else
echo '_SystemView report unavailable: rendering it failed (see the job log)._' > "$SV/notice.md"
fi
- name: Combine rig reports (one table per rig)
id: combine
run: |
shopt -s nullglob
SV="$RUNNER_TEMP/sysview"
dirs=(hil-reports/hil-report-*)
[ ${#dirs[@]} -eq 0 ] && echo "No HIL reports found"
{
echo "## Hardware-in-the-loop (HIL) Test Report"
echo
if [ ${#dirs[@]} -eq 0 ]; then
# No rig produced a report: PR selection matched no board anywhere, or the
# HIL jobs did not run at all. Post it rather than exiting, so a table from
# an earlier push is replaced instead of being left to look current.
echo "_No HIL run for this push (no affected boards, or hardware testing did not run)._"
fi
for d in "${dirs[@]}"; do
[ -d "$d" ] || continue
echo "### ${d#hil-reports/hil-report-}"
echo
if [ -e "$d/hil_report.unprepared" ]; then
# the leg could not prepare its report: never show Markdown it did not vouch for
echo "_HIL report unavailable: the latest attempt could not prepare it (see the job log)._"
else
cat "$d/hil_report.md" 2>/dev/null || echo "_no report produced_"
fi
echo
done
# the SystemView section, or why it is missing
for f in "$SV/report.md" "$SV/notice.md"; do
if [ -s "$f" ]; then echo; cat "$f"; break; fi
done
} > hil_combined.md
# Fork PRs can influence report content and this job posts in base-repo context, so
# neutralize @-mentions (insert a zero-width space) to prevent notification abuse.
zwsp=$(printf '\342\200\213')
sed -i -E "s/@([A-Za-z0-9_-])/@${zwsp}\1/g" hil_combined.md
cat hil_combined.md
echo "found=true" >> "$GITHUB_OUTPUT"
- name: Skip a superseded run
id: current
if: steps.combine.outputs.found == 'true'
env:
PR: ${{ needs.pr_number.outputs.number }}
RUN_ID: ${{ github.event.workflow_run.id }}
ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
if python3 .github/scripts/code_size_ci.py is-current --repo "$REPO" --pr "$PR" \
--run-id "$RUN_ID" --attempt "$ATTEMPT" --head-sha "$HEAD_SHA"; then
echo "post=true" >> "$GITHUB_OUTPUT"
fi
- name: Post HIL report as sticky PR comment
if: steps.current.outputs.post == 'true'
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: hil-report
path: hil_combined.md
number: ${{ needs.pr_number.outputs.number }}
# ---------------------------------------
# Code size of the PR's CI-pinned boards against the master push run of the commit it was
# built against (code_size_ci.py baseline): no base build, only the snapshots both Build
# runs uploaded. Artifacts are PR-built data, parsed only by the default branch's scripts
# and rendered escaped. Report only: this job never fails the PR.
# ---------------------------------------
code-size-comment:
needs: pr_number
if: >
github.event.workflow_run.conclusion != 'cancelled' &&
needs.pr_number.outputs.number != ''
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: read
actions: read
pull-requests: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
steps:
- name: Checkout default branch scripts
uses: actions/checkout@v6
with:
persist-credentials: false
# the code-size engine is agentrc's, at the default branch's pin; without it the
# comparison below fails and the comment says so
- name: Get agentrc
continue-on-error: true
run: python3 tools/get_deps.py lib/agentrc
# every step up to the freshness check must succeed, even with nothing to compare:
# a failed one would skip the post and leave an earlier push's table looking current
- name: Download code size snapshots
continue-on-error: true
uses: actions/download-artifact@v5
with:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
pattern: code-size-*
path: ${{ runner.temp }}/code-size/current
- name: Find the baseline
id: baseline
run: |
CS="$RUNNER_TEMP/code-size"
mkdir -p "$CS/current" "$CS/out"
# it reports its own failures in the info; a crash still must not skip the post
python3 .github/scripts/code_size_ci.py baseline --repo "$REPO" --current "$CS/current" \
--info "$CS/baseline.json" || echo "::warning::code_size_ci.py baseline crashed"
echo "run_id=$(jq -r '.run_id // empty' "$CS/baseline.json")" >> "$GITHUB_OUTPUT"
- name: Download baseline snapshots
if: steps.baseline.outputs.run_id != ''
continue-on-error: true
uses: actions/download-artifact@v5
with:
run-id: ${{ steps.baseline.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
pattern: code-size-*
path: ${{ runner.temp }}/code-size/base
- name: Compare against the baseline
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
CS="$RUNNER_TEMP/code-size"
if ! python3 tools/code_size.py compare "$CS/current" "$CS/base" -o "$CS/out" --symbols \
--baseline-info "$CS/baseline.json"; then
printf '## Code size\n\n_The comparison failed._\n' > "$CS/out/comment.md"
fi
echo "_[Full report]($RUN_URL)_" >> "$CS/out/comment.md"
# the job summary caps at 1 MiB
if [ -f "$CS/out/code-size.md" ]; then head -c 1000000 "$CS/out/code-size.md" >> "$GITHUB_STEP_SUMMARY"; fi
- name: Upload full report
continue-on-error: true
uses: actions/upload-artifact@v7
with:
name: code-size-report
path: ${{ runner.temp }}/code-size/out/code-size.*
- name: Skip a superseded run
id: current
env:
PR: ${{ needs.pr_number.outputs.number }}
RUN_ID: ${{ github.event.workflow_run.id }}
ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
if python3 .github/scripts/code_size_ci.py is-current --repo "$REPO" --pr "$PR" \
--run-id "$RUN_ID" --attempt "$ATTEMPT" --head-sha "$HEAD_SHA"; then
echo "post=true" >> "$GITHUB_OUTPUT"
fi
- name: Post code size as sticky PR comment
if: steps.current.outputs.post == 'true'
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: code-size
path: ${{ runner.temp }}/code-size/out/comment.md
number: ${{ needs.pr_number.outputs.number }}