summaryrefslogtreecommitdiff
path: root/.github/workflows
diff options
context:
space:
mode:
Diffstat (limited to '.github/workflows')
-rw-r--r--.github/workflows/build.yml370
-rw-r--r--.github/workflows/build_util.yml17
-rwxr-xr-x.github/workflows/ci_set_matrix.py110
-rw-r--r--.github/workflows/pr_comment.yml9
-rw-r--r--.github/workflows/pre-commit.yml5
5 files changed, 367 insertions, 144 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c8c597e50..8f6014f48 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,27 +41,100 @@ jobs:
- '.github/actions/**'
- '.github/workflows/build.yml'
- '.github/workflows/build_util.yml'
- - '.github/workflows/ci_set_matrix.py'
+ - '.github/scripts/**'
set-matrix:
runs-on: ubuntu-latest
outputs:
json: ${{ steps.set-matrix-json.outputs.matrix }}
hil_json: ${{ steps.set-matrix-json.outputs.hil_matrix }}
+ # one pair per rig job: hil-tinyusb (tinyusb.json minus esptool boards),
+ # hil-tinyusb-esp (esptool boards only), hil-tinyusb (hfp.json)
+ hil_args_tinyusb: ${{ steps.hil-select.outputs.args_tinyusb }}
+ hil_run_tinyusb: ${{ steps.hil-select.outputs.run_tinyusb }}
+ hil_args_tinyusb_esp: ${{ steps.hil-select.outputs.args_tinyusb_esp }}
+ hil_run_tinyusb_esp: ${{ steps.hil-select.outputs.run_tinyusb_esp }}
+ hil_args_hfp: ${{ steps.hil-select.outputs.args_hfp }}
+ hil_run_hfp: ${{ steps.hil-select.outputs.run_hfp }}
steps:
- name: Checkout TinyUSB
uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: HIL selection (PR only)
+ id: hil-select
+ if: github.event_name == 'pull_request'
+ env:
+ BASE_REF: ${{ github.base_ref }}
+ run: |
+ # Best-effort by design: set-matrix gates cmake, hil-build and every rig job,
+ # so a missing origin/<base>, a shallow-clone hiccup or a selector traceback
+ # must fall back to the FULL matrix (no --select, run=true, no args) instead
+ # of failing the job. Same fail-open shape as pr_comment.yml's `|| true`.
+ #
+ # The selector's own unit suite gates it (stdlib-only, seconds): a selector
+ # whose tests fail can still exit 0 with valid-but-WRONG JSON -- fail-open alone
+ # never catches that class, and the pre-commit hil-test hook is a separate,
+ # advisory workflow that nothing here can `needs:`. Test-failing selector =>
+ # full matrix, same as a crashing one.
+ SELECT_JSON=''
+ if ! python3 test/hil/test/test_hil_select.py; then
+ echo "::warning::hil_select unit suite failed - falling back to the full HIL matrix"
+ elif ! SELECT_JSON=$(python3 test/hil/helper/hil_select.py --base "origin/$BASE_REF" test/hil/tinyusb.json test/hil/hfp.json); then
+ echo "::warning::hil_select failed - falling back to the full HIL matrix"
+ SELECT_JSON=''
+ fi
+
+ # One args/run pair per rig job, split by flasher: a job whose own subset is
+ # empty skips explicitly instead of running a board filter that matches zero
+ # boards ("No tests were run." exits 0 and would read as a green HIL run).
+ OUT=''
+ if [ -n "$SELECT_JSON" ]; then
+ OUT=$(SELECT_JSON="$SELECT_JSON" python3 -c '
+ import json, os
+ s = json.loads(os.environ["SELECT_JSON"])
+ tin = s.get("args_flasher", {}).get("tinyusb.json", {})
+ legs = (("tinyusb", " ".join(a for f, a in sorted(tin.items()) if f != "esptool" and a)),
+ ("tinyusb_esp", tin.get("esptool", "")),
+ ("hfp", s.get("args", {}).get("hfp.json", "")))
+ for key, a in legs:
+ print("args_" + key + "=" + a)
+ print("run_" + key + "=" + ("true" if (s.get("full") or a) else "false"))
+ ') || OUT=''
+ if [ -z "$OUT" ]; then
+ echo "::warning::hil_select output unusable - falling back to the full HIL matrix"
+ SELECT_JSON=''
+ fi
+ fi
+ if [ -z "$OUT" ]; then
+ OUT=$(for k in tinyusb tinyusb_esp hfp; do printf 'args_%s=\nrun_%s=true\n' "$k" "$k"; done)
+ fi
+ echo "$OUT"
+ { echo "select=$SELECT_JSON"; echo "$OUT"; } >> $GITHUB_OUTPUT
- name: Generate matrix json
id: set-matrix-json
+ env:
+ SELECT: ${{ steps.hil-select.outputs.select }}
run: |
# build matrix
- MATRIX_JSON=$(python .github/workflows/ci_set_matrix.py)
+ MATRIX_JSON=$(python .github/scripts/ci_set_matrix.py)
echo "matrix=$MATRIX_JSON"
echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT
- # HIL matrix (merged from tinyusb + hifiphile configs)
- HIL_MATRIX_JSON=$(python test/hil/hil_ci_set_matrix.py test/hil/tinyusb.json test/hil/hfp.json)
+ # HIL matrix (merged from tinyusb + hifiphile configs), scoped on PRs.
+ # Scoping is best-effort too: fall back to the unscoped (full) matrix.
+ HIL_MATRIX_JSON=''
+ if [ -n "$SELECT" ]; then
+ HIL_MATRIX_JSON=$(python .github/scripts/hil_ci_set_matrix.py --select "$SELECT" test/hil/tinyusb.json test/hil/hfp.json) || HIL_MATRIX_JSON=''
+ if [ -z "$HIL_MATRIX_JSON" ]; then
+ echo "::warning::scoped HIL matrix failed - falling back to the full HIL matrix"
+ fi
+ fi
+ if [ -z "$HIL_MATRIX_JSON" ]; then
+ HIL_MATRIX_JSON=$(python .github/scripts/hil_ci_set_matrix.py test/hil/tinyusb.json test/hil/hfp.json)
+ fi
echo "hil_matrix=$HIL_MATRIX_JSON"
echo "hil_matrix=$HIL_MATRIX_JSON" >> $GITHUB_OUTPUT
@@ -271,23 +344,55 @@ jobs:
strategy:
fail-fast: false
matrix:
+ # These names are the bucket keys of .github/scripts/hil_ci_set_matrix.py: every
+ # non-esptool roster board must land in one of them (esptool boards go to
+ # 'esp-idf', built by hil-build-esp below). hil_ci_set_matrix.py rejects a
+ # board whose "toolchain" is not a bucket, so a new bucket must be added in
+ # both places.
+ #
+ # INVARIANT the PR-scoped skip cascade rests on: hil_run_tinyusb / hil_run_hfp
+ # true => hil-build has at least one non-empty leg. It holds because those
+ # flags count only non-esptool boards and every such board builds here. It
+ # would break if an esptool board were added to hfp.json, because
+ # hil_args_hfp is NOT flasher-split: the hfp leg of hil-tinyusb would want to
+ # run while hil-build (and therefore that leg) skipped.
toolchain:
- 'arm-gcc'
- 'riscv-gcc'
- - 'esp-idf'
with:
build-system: 'cmake'
toolchain: ${{ matrix.toolchain }}
build-args: ${{ toJSON(fromJSON(needs.set-matrix.outputs.hil_json)[matrix.toolchain]) }}
upload-artifacts: true
+ # esp-idf builds are by far the slowest; keep them out of hil-build so the main
+ # hil-tinyusb run starts as soon as the fast toolchains finish (esp boards get
+ # their own hil-tinyusb-esp run gated only on this job)
+ hil-build-esp:
+ needs: [ check-paths, set-matrix ]
+ if: needs.check-paths.outputs.code_changed == 'true' && github.repository_owner == 'hathach'
+ uses: ./.github/workflows/build_util.yml
+ with:
+ build-system: 'cmake'
+ toolchain: 'esp-idf'
+ build-args: ${{ toJSON(fromJSON(needs.set-matrix.outputs.hil_json)['esp-idf']) }}
+ upload-artifacts: true
+
# ---------------------------------------
# Hardware in the loop (HIL)
# self-hosted on local VM, for attached hardware checkout HIL_JSON
# ---------------------------------------
hil-tinyusb:
- needs: hil-build
+ needs: [ hil-build, set-matrix ]
name: hil-tinyusb (${{ matrix.display }})
+ # Above hil_test.py's pool guard (HIL_POOL_TIMEOUT, 60 min) so the guard fires first
+ # and still gets to write its report. The 30 min on top is what the job pays OUTSIDE
+ # the guard clock: workspace cleanup, checkout, the multi-board artifact merge and
+ # the D-state note before it; kill_worker_children, shutdown_pool's 30 s grace, the
+ # report write and the upload after it. On a multi-stray convoy that tail alone is
+ # minutes, and a ceiling below guard+tail cancels the job before hil_report.md exists
+ # -- the inversion this branch removes. Both legs share the script and the guard.
+ timeout-minutes: 90
strategy:
fail-fast: false
matrix:
@@ -295,26 +400,39 @@ jobs:
- display: tinyusb.json
runner: [ self-hosted, X64, hathach, hardware-in-the-loop ]
hil_json: test/hil/tinyusb.json
+ # esptool-flashed (espressif) boards run in hil-tinyusb-esp,
+ # gated on the slow esp-idf build
+ test_args: '--exclude-flasher esptool'
- display: hfp.json
runner: [ self-hosted, Linux, X64, hifiphile ]
hil_json: test/hil/hfp.json
+ test_args: ''
runs-on: ${{ matrix.runner }}
env:
+ # HIL_POOL_TIMEOUT deliberately unset: hil_test.py's 60 min default is below every
+ # ceiling here, so ceiling > guard holds by construction. Pin it to SHORTEN a run
+ # only -- pinning it above a ceiling re-inverts the two.
HIL_JSON: ${{ matrix.hil_json }}
steps:
- - name: Set HIL report dir (sibling of workspace; persists across run attempts)
- run: echo "HIL_REPORT_DIR=$(dirname "$GITHUB_WORKSPACE")/hil-report" >> "$GITHUB_ENV"
+ - name: Set HIL report dir (per run+job; persists across run attempts)
+ run: |
+ # one report dir per (run id, job): re-run attempts find their own report/spec,
+ # and interleaved runs of other PRs/jobs on the same runner cannot clobber them
+ BASE="$(dirname "$GITHUB_WORKSPACE")/hil-report"
+ # prune per-run dirs older than 2 weeks
+ find "$BASE" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} + 2>/dev/null || true
+ echo "HIL_REPORT_DIR=$BASE/${GITHUB_RUN_ID}-$(basename "${{ matrix.display }}" .json)" >> "$GITHUB_ENV"
- - name: Get Skip Boards from previous run
+ - name: Get re-run spec from previous attempt
if: github.run_attempt != '1'
run: |
- if [ -f "${{ env.HIL_JSON }}.skip" ]; then
- SKIP_BOARDS=$(cat "${{ env.HIL_JSON }}.skip")
- else
- SKIP_BOARDS=""
- fi
- echo "SKIP_BOARDS=$SKIP_BOARDS"
- echo "SKIP_BOARDS=$SKIP_BOARDS" >> $GITHUB_ENV
+ # the report dir is keyed by run id, so a spec here can only have been
+ # written by an earlier attempt of THIS run
+ SPEC="$HIL_REPORT_DIR/$(basename "${{ env.HIL_JSON }}").failed"
+ RERUN_ARGS=""
+ [ -f "$SPEC" ] && RERUN_ARGS=$(cat "$SPEC")
+ echo "RERUN_ARGS=$RERUN_ARGS"
+ echo "RERUN_ARGS=$RERUN_ARGS" >> $GITHUB_ENV
- name: Clean workspace
run: |
@@ -333,15 +451,33 @@ jobs:
merge-multiple: true
- name: Test on actual hardware
+ # Single attempt per test (--retry 1), no in-run second pass: a broken fixture
+ # fails fast instead of holding the runner (and other PRs' HIL jobs) for hours.
+ env:
+ # tinyusb.json minus the esptool boards (they run in hil-tinyusb-esp): each
+ # job gates on its own flasher subset, never on a rig-wide flag
+ SEL_ARGS_TINYUSB: ${{ needs.set-matrix.outputs.hil_args_tinyusb }}
+ SEL_RUN_TINYUSB: ${{ needs.set-matrix.outputs.hil_run_tinyusb }}
+ SEL_ARGS_HFP: ${{ needs.set-matrix.outputs.hil_args_hfp }}
+ SEL_RUN_HFP: ${{ needs.set-matrix.outputs.hil_run_hfp }}
run: |
- python3 test/hil/hil_test.py ${{ env.HIL_JSON }} $SKIP_BOARDS || \
- (if [ -f "${{ env.HIL_JSON }}.skip" ]; then
- SKIP_BOARDS=$(cat "${{ env.HIL_JSON }}.skip")
- echo "Re-running with SKIP_BOARDS=$SKIP_BOARDS"
- python3 test/hil/hil_test.py ${{ env.HIL_JSON }} $SKIP_BOARDS
- else
- exit 1
- fi)
+ case "$HIL_JSON" in
+ *tinyusb.json) SEL_ARGS="$SEL_ARGS_TINYUSB"; SEL_RUN="$SEL_RUN_TINYUSB" ;;
+ *hfp.json) SEL_ARGS="$SEL_ARGS_HFP"; SEL_RUN="$SEL_RUN_HFP" ;;
+ esac
+ if [ "$SEL_RUN" = "false" ]; then
+ echo "HIL skipped by PR selection (no affected boards on this rig)"
+ # leave a marker so the combined PR comment says so instead of dropping the
+ # section (and leaving a stale table from an earlier push in its place)
+ mkdir -p "$HIL_REPORT_DIR"
+ echo "_Skipped by PR selection: no affected boards on this rig._" > "$HIL_REPORT_DIR/hil_report.md"
+ exit 0
+ fi
+ # a re-run spec is already a subset of the selection (only the boards/tests
+ # that failed); -b/-bt accumulate, so keeping SEL_ARGS here would re-run the
+ # entire original selection instead of just what failed
+ if [ -n "$RERUN_ARGS" ]; then SEL_ARGS=''; fi
+ python3 test/hil/hil_test.py --retry 1 ${{ matrix.test_args }} $SEL_ARGS ${{ env.HIL_JSON }} $RERUN_ARGS
- name: Upload HIL report
if: always() && github.event_name == 'pull_request'
@@ -353,6 +489,92 @@ jobs:
overwrite: true
# ---------------------------------------
+ # Hardware in the loop (HIL) - espressif boards only
+ # Same rig as hil-tinyusb (tinyusb.json) but gated only on the slow esp-idf build,
+ # so the main run does not wait for it. Per-board flocks arbitrate the shared rig;
+ # the runner has a single job slot, so the two HIL jobs never overlap - adding a
+ # second slot would double the per-controller flash/usbtest budgets.
+ # ---------------------------------------
+ hil-tinyusb-esp:
+ needs: [ hil-build-esp, set-matrix ]
+ name: hil-tinyusb (tinyusb-esp.json)
+ runs-on: [ self-hosted, X64, hathach, hardware-in-the-loop ]
+ # above hil_test.py's pool guard (60 min) with room for the pre-pool checkout
+ # and the post-guard sweep + report upload, so its own guard still writes a report;
+ # only a job wedged past that (unkillable D-state worker) hits this ceiling, which
+ # must exist because the runner has one job slot and holds every queued job hostage
+ timeout-minutes: 90
+ env:
+ HIL_JSON: test/hil/tinyusb.json
+ TEST_ARGS: '--flasher esptool'
+ steps:
+ - name: Set HIL report dir (per run+job; persists across run attempts)
+ run: |
+ # one report dir per (run id, job): re-run attempts find their own report/spec,
+ # and interleaved runs of other PRs/jobs on the same runner cannot clobber them
+ BASE="$(dirname "$GITHUB_WORKSPACE")/hil-report"
+ # prune per-run dirs older than 2 weeks
+ find "$BASE" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} + 2>/dev/null || true
+ echo "HIL_REPORT_DIR=$BASE/${GITHUB_RUN_ID}-tinyusb-esp" >> "$GITHUB_ENV"
+
+ - name: Get re-run spec from previous attempt
+ if: github.run_attempt != '1'
+ run: |
+ # the report dir is keyed by run id, so a spec here can only have been
+ # written by an earlier attempt of THIS run
+ SPEC="$HIL_REPORT_DIR/$(basename "${{ env.HIL_JSON }}").failed"
+ RERUN_ARGS=""
+ [ -f "$SPEC" ] && RERUN_ARGS=$(cat "$SPEC")
+ echo "RERUN_ARGS=$RERUN_ARGS"
+ echo "RERUN_ARGS=$RERUN_ARGS" >> $GITHUB_ENV
+
+ - name: Clean workspace
+ run: |
+ echo "Cleaning up for the first run"
+ rm -rf "${{ github.workspace }}"
+ mkdir -p "${{ github.workspace }}"
+
+ - name: Checkout TinyUSB
+ uses: actions/checkout@v6
+
+ - name: Download Artifacts
+ uses: actions/download-artifact@v5
+ with:
+ pattern: binaries-esp-idf-*
+ path: cmake-build
+ merge-multiple: true
+
+ - name: Test on actual hardware
+ env:
+ # esptool subset of tinyusb.json: this job must gate on its own boards, not
+ # on the rig-wide flag (which would run a filter matching zero boards)
+ SEL_ARGS: ${{ needs.set-matrix.outputs.hil_args_tinyusb_esp }}
+ SEL_RUN: ${{ needs.set-matrix.outputs.hil_run_tinyusb_esp }}
+ run: |
+ if [ "$SEL_RUN" = "false" ]; then
+ echo "HIL skipped by PR selection (no affected esptool boards)"
+ # leave a marker so the combined PR comment says so instead of dropping the
+ # section (and leaving a stale table from an earlier push in its place)
+ mkdir -p "$HIL_REPORT_DIR"
+ echo "_Skipped by PR selection: no affected esptool boards._" > "$HIL_REPORT_DIR/hil_report.md"
+ exit 0
+ fi
+ # a re-run spec is already a subset of the selection (only the boards/tests
+ # that failed); -b/-bt accumulate, so keeping SEL_ARGS here would re-run the
+ # entire original selection instead of just what failed
+ if [ -n "$RERUN_ARGS" ]; then SEL_ARGS=''; fi
+ python3 test/hil/hil_test.py --retry 1 $TEST_ARGS $SEL_ARGS ${{ env.HIL_JSON }} $RERUN_ARGS
+
+ - name: Upload HIL report
+ if: always() && github.event_name == 'pull_request'
+ uses: actions/upload-artifact@v7
+ with:
+ name: hil-report-tinyusb-esp.json
+ path: ${{ env.HIL_REPORT_DIR }}/hil_report.md
+ if-no-files-found: ignore
+ overwrite: true
+
+ # ---------------------------------------
# Hardware in the loop (HIL)
# self-hosted by HFP, build with IAR toolchain, for attached hardware checkout test/hil/hfp.json
# Since IAR Token secret is not passed to forked PR, only build non-forked PR
@@ -364,7 +586,13 @@ jobs:
github.repository_owner == 'hathach' &&
!(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true)
runs-on: [ self-hosted, Linux, X64, hifiphile ]
- timeout-minutes: 30
+ # Unlike the hil-tinyusb jobs, this one BUILDS with IAR in the same job before running
+ # hil_test.py -- hfp.json's 3 boards, 4 variant entries, "up to 30 minutes" (see the
+ # comment above the selection step). The ceiling has to cover build + the 60 min pool
+ # guard + overhead, or GitHub cancels before the guard can write its report -- the
+ # inversion this branch removes. 30 + 60 = 90; the remaining 30 is the full-history
+ # checkout, get_deps, the post-guard sweep and the report upload.
+ timeout-minutes: 120
env:
IAR_LMS_BEARER_TOKEN: ${{ secrets.IAR_LMS_BEARER_TOKEN }}
PYTHONUNBUFFERED: '1'
@@ -381,23 +609,87 @@ jobs:
- name: Checkout TinyUSB
uses: actions/checkout@v6
+ with:
+ # full history: the "HIL selection" step below needs
+ # merge-base(HEAD, origin/<base_ref>) for PR-scoped selection
+ fetch-depth: 0
+
+ # Computed BEFORE the build: the IAR build is four boards and up to 30 minutes on
+ # a runner that hil-tinyusb (hfp.json) also needs, so an unaffected PR must release
+ # it immediately instead of building everything and then skipping. The selection
+ # also narrows what gets built.
+ # This job has no needs: on set-matrix (it must run even if that unrelated job
+ # fails), so it computes its own selection instead of reading set-matrix's outputs.
+ - name: HIL selection (PR only)
+ if: github.event_name == 'pull_request'
+ env:
+ BASE_REF: ${{ github.base_ref }}
+ run: |
+ # Best-effort: this job is deliberately decoupled from set-matrix so unrelated
+ # failures cannot kill hfp coverage - a selector failure here must likewise
+ # fall back to the full hfp matrix (no hil_select.json, no SEL_* vars), never
+ # fail the job.
+ if ! python3 test/hil/test/test_hil_select.py; then
+ echo "::warning::hil_select unit suite failed - running the full hfp matrix"
+ rm -f hil_select.json
+ exit 0
+ fi
+ if ! python3 test/hil/helper/hil_select.py --base "origin/$BASE_REF" test/hil/hfp.json > hil_select.json; then
+ echo "::warning::hil_select failed - running the full hfp matrix"
+ rm -f hil_select.json
+ exit 0
+ fi
+ # hil_select.json is passed to hil_ci_set_matrix.py --select below to scope the
+ # build; it already honours full=true by ignoring the board list.
+ # The hil_test.py args go to a file, never to $GITHUB_ENV: they are derived
+ # from roster board names, which a PR can edit. Only SEL_RUN (a literal
+ # true/false computed here, needed by the step-level `if:`) goes to the env.
+ if ! SEL_RUN=$(python3 -c '
+ import json
+ s = json.load(open("hil_select.json"))
+ a = s["args"]["hfp.json"]
+ open("hil_sel_args.txt", "w").write(a)
+ print("true" if (s["full"] or a) else "false")
+ '); then
+ echo "::warning::hil_select output unusable - running the full hfp matrix"
+ rm -f hil_select.json hil_sel_args.txt
+ exit 0
+ fi
+ echo "SEL_RUN=$SEL_RUN"
+ echo "SEL_RUN=$SEL_RUN" >> $GITHUB_ENV
- name: Get build boards
+ if: env.SEL_RUN != 'false'
run: |
- MATRIX_JSON=$(python test/hil/hil_ci_set_matrix.py test/hil/hfp.json)
- BUILD_ARGS=$(echo $MATRIX_JSON | jq -r '.["arm-gcc"] | join(" ")')
+ if [ -f hil_select.json ]; then
+ MATRIX_JSON=$(python .github/scripts/hil_ci_set_matrix.py --select "$(cat hil_select.json)" test/hil/hfp.json)
+ else
+ MATRIX_JSON=$(python .github/scripts/hil_ci_set_matrix.py test/hil/hfp.json)
+ fi
+ # Each variant carries its own --build-name/--cflag, which are global to a
+ # single build.py invocation — so keep one matrix entry per line and build
+ # them one at a time (joining would leak a variant's flags onto every board).
+ echo "$MATRIX_JSON" | jq -r '.["arm-gcc"][]' > hil_build_entries.txt
+ cat hil_build_entries.txt
+ BUILD_ARGS=$(echo "$MATRIX_JSON" | jq -r '.["arm-gcc"] | join(" ")')
echo "BUILD_ARGS=$BUILD_ARGS"
echo "BUILD_ARGS=$BUILD_ARGS" >> $GITHUB_ENV
- name: Get Dependencies
+ if: env.SEL_RUN != 'false'
run: python3 tools/get_deps.py $BUILD_ARGS
- name: Build
+ if: env.SEL_RUN != 'false'
+ # Bounded SEPARATELY from the job. This is the only HIL job that builds inline
+ # (hil-tinyusb downloads artifacts), and the job ceiling went 30 -> 120 to give the
+ # HIL step room -- which would hand a stalled IAR build the whole two hours on the
+ # shared self-hosted runner, never reaching hil_test.py or the report upload. That
+ # is the stranded-runner-with-no-report failure this branch exists to prevent.
+ # Typical full build here is a few minutes; 30 leaves generous headroom.
+ timeout-minutes: 30
run: |
- # Each variant carries its own --build-name/--cflag, which are global to a
- # single build.py invocation — so build one matrix entry at a time rather
- # than joining them (joining would leak a variant's flags onto every board).
- readarray -t ENTRIES < <(python test/hil/hil_ci_set_matrix.py test/hil/hfp.json | jq -r '.["arm-gcc"][]')
+ readarray -t ENTRIES < hil_build_entries.txt
for entry in "${ENTRIES[@]}"; do
echo "+ tools/build.py --toolchain iar $entry"
python3 tools/build.py --toolchain iar $entry
@@ -405,7 +697,21 @@ jobs:
- name: Test on actual hardware (hardware in the loop)
run: |
- python3 test/hil/hil_test.py hfp.json
+ if [ "$SEL_RUN" = "false" ]; then
+ echo "HIL skipped by PR selection (no affected boards on this rig)"
+ # leave a marker so the combined PR comment says so instead of dropping
+ # the section (and leaving a stale table from an earlier push)
+ echo "_Skipped by PR selection: no affected boards on this rig._" > hil_report.md
+ exit 0
+ fi
+ # empty/absent on a non-PR event or a selector fallback -> full hfp matrix
+ SEL_ARGS=$(cat hil_sel_args.txt 2>/dev/null || true)
+ # --retry 1, like the other two HIL legs. The pool guard is a FLAT 3600s and
+ # does NOT scale with max_retry, so argparse's default of 3 would multiply the
+ # serialized usbtest tail (hfp.json runs four batteries) by three against an
+ # unchanged guard -- on a runner with a single job slot that queues every other
+ # job behind it.
+ python3 test/hil/hil_test.py --retry 1 $SEL_ARGS hfp.json
- name: Upload HIL report
if: always() && github.event_name == 'pull_request'
diff --git a/.github/workflows/build_util.yml b/.github/workflows/build_util.yml
index 2532caebe..02f16488a 100644
--- a/.github/workflows/build_util.yml
+++ b/.github/workflows/build_util.yml
@@ -39,6 +39,21 @@ on:
jobs:
family:
+ # PR-scoped HIL selection can produce an empty build-args list for a toolchain
+ # (e.g. a dwc2-only change with no riscv boards affected); an empty matrix
+ # vector fails the job outright ("Matrix vector 'arg' does not contain any
+ # values"), so skip cleanly instead.
+ #
+ # Why that is safe for callers: GitHub SKIPS the dependents of a skipped
+ # `needs:` job, so this only works because the caller (hil-build) is itself a
+ # *matrix* job - a matrix with one skipped leg and one successful leg
+ # aggregates to success, and its dependents run.
+ #
+ # NOT covered: if every leg is empty the whole caller job skips, and so does
+ # everything that needs it. That is fine only because an all-empty selection
+ # means no board was selected for those rigs, so the rig jobs would have had
+ # nothing to run anyway (see the invariant on hil-build in build.yml).
+ if: inputs.build-args != '[]'
runs-on: ${{ inputs.os }}
strategy:
fail-fast: false
@@ -99,7 +114,7 @@ jobs:
if: inputs.upload-artifacts == true && inputs.code-changed == true
uses: actions/upload-artifact@v7
with:
- name: binaries-${{ matrix.arg }}
+ name: binaries-${{ inputs.toolchain }}-${{ matrix.arg }}
path: |
cmake-build/cmake-build-*/*/*/*.elf
cmake-build/cmake-build-*/*/*/*.bin
diff --git a/.github/workflows/ci_set_matrix.py b/.github/workflows/ci_set_matrix.py
deleted file mode 100755
index dc0d3871f..000000000
--- a/.github/workflows/ci_set_matrix.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python3
-import json
-
-# toolchain, url
-toolchain_list = [
- "aarch64-gcc",
- "arm-clang",
- "arm-iar",
- "arm-gcc",
- "esp-idf",
- "ft9xx-gcc",
- "msp430-gcc",
- "riscv-gcc",
- "rx-gcc"
-]
-
-# family: [supported toolchain]
-family_list = {
- "at32f402_405": ["arm-gcc"],
- "at32f403a_407": ["arm-gcc"],
- "at32f413": ["arm-gcc"],
- "at32f415": ["arm-gcc"],
- "at32f423": ["arm-gcc"],
- "at32f425": ["arm-gcc"],
- "at32f435_437": ["arm-gcc"],
- "at32f45x": ["arm-gcc"],
- "broadcom_32bit": ["arm-gcc"],
- "broadcom_64bit": ["aarch64-gcc"],
- "ch32f20x": ["arm-gcc"],
- "ch32v10x": ["riscv-gcc"],
- "ch32v20x": ["riscv-gcc"],
- "ch32v30x": ["riscv-gcc"],
- "ch583": ["riscv-gcc"],
- "da1469x": ["arm-gcc"],
- "fomu": ["riscv-gcc"],
- "ft9xx": ["ft9xx-gcc"],
- "gd32vf103": ["riscv-gcc"],
- "hpmicro": ["riscv-gcc"],
- "imxrt": ["arm-gcc", "arm-clang"],
- "kinetis_k": ["arm-gcc"],
- "kinetis_k32l": ["arm-gcc"],
- "kinetis_kl": ["arm-gcc"],
- "lpc11": ["arm-gcc", "arm-clang"],
- "lpc13": ["arm-gcc", "arm-clang"],
- "lpc15": ["arm-gcc", "arm-clang"],
- "lpc17": ["arm-gcc", "arm-clang"],
- "lpc18": ["arm-gcc", "arm-clang"],
- "lpc40": ["arm-gcc", "arm-clang"],
- "lpc43": ["arm-gcc", "arm-clang"],
- "lpc51": ["arm-gcc", "arm-clang"],
- "lpc54": ["arm-gcc", "arm-clang"],
- "lpc55": ["arm-gcc", "arm-clang"],
- "maxim": ["arm-gcc"],
- "mcx": ["arm-gcc"],
- "mm32": ["arm-gcc"],
- "msp430": ["msp430-gcc"],
- "msp432e4": ["arm-gcc"],
- "nrf": ["arm-gcc", "arm-clang"],
- "nuc100_120": ["arm-gcc"],
- "nuc121_125": ["arm-gcc"],
- "nuc126": ["arm-gcc"],
- "nuc505": ["arm-gcc"],
- "ra": ["arm-gcc"],
- "rp2040": ["arm-gcc"],
- "rw61x": ["arm-gcc"],
- "rx": ["rx-gcc"],
- "samd11": ["arm-gcc", "arm-clang"],
- "samd2x_l2x": ["arm-gcc", "arm-clang"],
- "samd5x_e5x": ["arm-gcc", "arm-clang"],
- "samg": ["arm-gcc", "arm-clang"],
- "stm32c0": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32c5": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f0": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f1": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f2": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f3": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f4": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32f7": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32g0": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32g4": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32h5": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32h7": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32h7rs": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32l0": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32l4": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32n6": ["arm-gcc"],
- "stm32u0": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32u5": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32wb": ["arm-gcc", "arm-clang", "arm-iar"],
- "stm32wba": ["arm-gcc", "arm-clang", "arm-iar"],
- "tm4c": ["arm-gcc"],
- "xmc4000": ["arm-gcc"],
- # S3, P4 will be built by hil test
- # "-bespressif_s3_devkitm": ["esp-idf"],
- # "-bespressif_p4_function_ev": ["esp-idf"],
-}
-
-
-def set_matrix_json():
- matrix = {}
- for toolchain in toolchain_list:
- filtered_families = [family for family, supported_toolchain in family_list.items() if
- toolchain in supported_toolchain]
- matrix[toolchain] = filtered_families
-
- print(json.dumps(matrix))
-
-
-if __name__ == '__main__':
- set_matrix_json()
diff --git a/.github/workflows/pr_comment.yml b/.github/workflows/pr_comment.yml
index 4d50817b4..868e56405 100644
--- a/.github/workflows/pr_comment.yml
+++ b/.github/workflows/pr_comment.yml
@@ -101,7 +101,16 @@ jobs:
shopt -s nullglob
dirs=(hil-reports/hil-report-*)
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 reports found"
+ {
+ echo "## Hardware-in-the-loop (HIL) Test Report"
+ echo
+ echo "_No HIL run for this push (no affected boards, or hardware testing did not run)._"
+ } > hil_combined.md
+ echo "found=true" >> "$GITHUB_OUTPUT"
exit 0
fi
{
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index b9bfaf9b6..09f912bd3 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -24,10 +24,13 @@ jobs:
- name: Get Dependencies
run: |
- gem install ceedling
+ # pinned: 1.1.0 breaks mock preprocessing on this project ("Failed to read
+ # _build/test/preprocess/.../raw/*.h for comment stripping"); revisit on next release
+ gem install ceedling -v 1.0.1
#cd test/unit-test
#ceedling test:all
+ # runs --all-files, so the hil-test hook fires here regardless of its `files:` scope
- name: Run pre-commit
uses: pre-commit/[email protected]