mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 11:02:16 +00:00
hil, ci: scope HIL builds and tests to the boards a PR affects Add test/hil/hil_select.py, a stdlib-only selector that maps a PR diff to the rig boards, tests and BSP families a change can affect, and wire it into CI so pull requests build and run only those. A port change picks its families' boards, a class change picks the examples enabling that class, and device/host changes prune the other role. Anything unclassified — infra, an unmapped port, a selector error — falls back to the full matrix, and push/schedule runs are untouched. Move the shared example lists to hil_examples.py; 54 hardware-free tests cover the rules.
679 lines
28 KiB
YAML
679 lines
28 KiB
YAML
name: Build
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
branches: [master]
|
|
pull_request:
|
|
release:
|
|
types: [ published ]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
|
|
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
|
|
|
jobs:
|
|
# Check if the code changes and we need to run ci build
|
|
# Cannot use paths filter in the on-event since we want this workflow to run even when there are no code changes, to register the commit chain
|
|
check-paths:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
code_changed: ${{ steps.filter.outputs.code }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 2 # Needed for push commit comparison
|
|
- uses: dorny/paths-filter@v4
|
|
id: filter
|
|
with:
|
|
filters: |
|
|
code:
|
|
- 'src/**'
|
|
- 'examples/**'
|
|
- 'lib/**'
|
|
- 'hw/**'
|
|
- 'test/hil/**'
|
|
- 'tools/build.py'
|
|
- 'tools/get_deps.py'
|
|
- '.github/actions/**'
|
|
- '.github/workflows/build.yml'
|
|
- '.github/workflows/build_util.yml'
|
|
- '.github/workflows/ci_set_matrix.py'
|
|
|
|
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`.
|
|
SELECT_JSON=''
|
|
if ! python3 test/hil/test_hil_select.py; then
|
|
echo "::error::hil_select unit tests failed - falling back to the full HIL matrix"
|
|
elif ! SELECT_JSON=$(python3 test/hil/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)
|
|
echo "matrix=$MATRIX_JSON"
|
|
echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT
|
|
|
|
# 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 test/hil/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 test/hil/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
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# CMake build: only one board per family (first alphabetically). Full build is done by CircleCI in PR
|
|
# Note:
|
|
# For Make and IAR build: will be done on CircleCI only (one random per family as well)
|
|
# ------------------------------------------------------------------------------
|
|
cmake:
|
|
needs: [ check-paths, set-matrix ]
|
|
uses: ./.github/workflows/build_util.yml
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
toolchain:
|
|
- 'aarch64-gcc'
|
|
#- 'arm-clang'
|
|
- 'arm-gcc'
|
|
#- 'esp-idf'
|
|
- 'ft9xx-gcc'
|
|
- 'msp430-gcc'
|
|
- 'riscv-gcc'
|
|
with:
|
|
build-system: 'cmake'
|
|
toolchain: ${{ matrix.toolchain }}
|
|
build-args: ${{ toJSON(fromJSON(needs.set-matrix.outputs.json)[matrix.toolchain]) }}
|
|
build-options: '--one-first'
|
|
upload-metrics: true
|
|
upload-artifacts: false
|
|
upload-membrowse: true
|
|
code-changed: ${{ needs.check-paths.outputs.code_changed == 'true' }}
|
|
secrets: inherit
|
|
|
|
code-metrics:
|
|
needs: [ check-paths, cmake ]
|
|
if: needs.check-paths.outputs.code_changed == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
contents: write
|
|
steps:
|
|
- name: Checkout TinyUSB
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-tags: ${{ github.event_name == 'release' }}
|
|
|
|
- name: Download Artifacts
|
|
uses: actions/download-artifact@v5
|
|
with:
|
|
pattern: metrics-*
|
|
path: cmake-build
|
|
merge-multiple: true
|
|
|
|
- name: Aggregate Code Metrics
|
|
run: |
|
|
python tools/get_deps.py
|
|
python tools/metrics.py combine -j -m -f tinyusb/src cmake-build/*/metrics.json
|
|
|
|
- name: Upload Metrics Artifact
|
|
if: github.event_name == 'push' || github.event_name == 'release'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: metrics-tinyusb
|
|
path: metrics.json
|
|
|
|
- name: Download Base Branch Metrics
|
|
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
|
uses: dawidd6/action-download-artifact@v11
|
|
with:
|
|
workflow: build.yml
|
|
workflow_conclusion: ''
|
|
branch: ${{ github.base_ref }}
|
|
name: metrics-tinyusb
|
|
path: base-metrics
|
|
continue-on-error: true
|
|
|
|
- name: Download Previous Release Asset
|
|
if: github.event_name == 'release'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
PREV_TAG=$(git tag --sort=-creatordate | head -n 2 | tail -n 1)
|
|
echo "Previous Release: $PREV_TAG"
|
|
echo "PREV_TAG=$PREV_TAG" >> $GITHUB_ENV
|
|
|
|
mkdir -p base-metrics
|
|
gh release download $PREV_TAG -p metrics.json -D base-metrics || echo "No metrics.json found in $PREV_TAG release"
|
|
|
|
- name: Compare with Base Branch
|
|
if: github.event_name != 'push'
|
|
run: |
|
|
if [ -f base-metrics/metrics.json ]; then
|
|
python tools/metrics.py compare -m -f tinyusb/src base-metrics/metrics.json metrics.json
|
|
cat metrics_compare.md
|
|
else
|
|
echo "No base metrics found, skipping comparison"
|
|
cp metrics.md metrics_compare.md
|
|
fi
|
|
|
|
- name: Upload Release Assets
|
|
if: github.event_name == 'release'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
CURR_TAG=${{ github.event.release.tag_name }}
|
|
COMPARE_FILE="metrics_compare_${CURR_TAG}-${PREV_TAG}.md"
|
|
mv metrics_compare.md $COMPARE_FILE
|
|
gh release upload $CURR_TAG metrics.json $COMPARE_FILE
|
|
|
|
- name: Upload Metrics Comment Artifact
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: metrics-comment
|
|
path: |
|
|
metrics_compare.md
|
|
metrics.json
|
|
|
|
- name: Post Code Metrics as PR Comment
|
|
if: (github.event_name == 'workflow_dispatch') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
|
uses: marocchino/sticky-pull-request-comment@v2
|
|
with:
|
|
header: code-metrics
|
|
path: metrics_compare.md
|
|
|
|
# ---------------------------------------
|
|
# Keep the metrics baseline available on no-code-change pushes
|
|
# The code-metrics job only runs (and uploads metrics-tinyusb) when code changed, so a
|
|
# workflow/docs-only push to master would leave the latest run without a baseline for PRs
|
|
# to compare against. Carry the previous artifact forward so the baseline is never missing.
|
|
# ---------------------------------------
|
|
metrics-carry-forward:
|
|
needs: [ check-paths ]
|
|
if: github.event_name == 'push' && needs.check-paths.outputs.code_changed != 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Download previous metrics baseline from this branch
|
|
uses: dawidd6/action-download-artifact@v11
|
|
with:
|
|
workflow: build.yml
|
|
workflow_conclusion: '' # any conclusion, matching the PR-side baseline download
|
|
search_artifacts: true # scan back past runs that lack the artifact (e.g. earlier no-code pushes)
|
|
branch: ${{ github.ref_name }}
|
|
name: metrics-tinyusb
|
|
path: .
|
|
if_no_artifact_found: warn
|
|
continue-on-error: true # best-effort: never make a no-code push red
|
|
|
|
- name: Re-publish baseline so the latest run keeps it
|
|
if: hashFiles('metrics.json') != ''
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: metrics-tinyusb
|
|
path: metrics.json
|
|
|
|
# ---------------------------------------
|
|
# Build Make/CMake on Windows/MacOS
|
|
# ---------------------------------------
|
|
build-os:
|
|
needs: [ check-paths ]
|
|
if: needs.check-paths.outputs.code_changed == 'true'
|
|
uses: ./.github/workflows/build_util.yml
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ windows-latest, macos-latest ]
|
|
build-system: [ 'make', 'cmake' ]
|
|
with:
|
|
os: ${{ matrix.os }}
|
|
build-system: ${{ matrix.build-system }}
|
|
toolchain: 'arm-gcc-${{ matrix.os }}'
|
|
build-args: '["stm32h7rs"]'
|
|
build-options: '--one-random'
|
|
|
|
# ---------------------------------------
|
|
# Zephyr
|
|
# ---------------------------------------
|
|
zephyr:
|
|
needs: [ check-paths ]
|
|
# skip zephyr build due to failed build, fix later
|
|
if: false
|
|
#if: needs.check-paths.outputs.code_changed == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout TinyUSB
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Zephyr project
|
|
uses: zephyrproject-rtos/action-zephyr-setup@v1
|
|
with:
|
|
app-path: examples
|
|
toolchains: arm-zephyr-eabi
|
|
|
|
- name: Build
|
|
run: |
|
|
west build -b nrf52840dk -d examples/device/cdc_msc/build examples/device/cdc_msc -- -DRTOS=zephyr
|
|
west build -b nrf52840dk -d examples/device/msc_dual_lun/build examples/device/msc_dual_lun -- -DRTOS=zephyr
|
|
|
|
# ---------------------------------------
|
|
# Hardware in the loop (HIL)
|
|
# Run on PR only (hil-tinyusb), hil-hfp-iar only run on non-forked PR
|
|
# ---------------------------------------
|
|
hil-build:
|
|
needs: [ check-paths, set-matrix ]
|
|
if: needs.check-paths.outputs.code_changed == 'true' && github.repository_owner == 'hathach'
|
|
uses: ./.github/workflows/build_util.yml
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# These names are the bucket keys of test/hil/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'
|
|
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, set-matrix ]
|
|
name: hil-tinyusb (${{ matrix.display }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- 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_JSON: ${{ matrix.hil_json }}
|
|
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}-$(basename "${{ matrix.display }}" .json)" >> "$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-*
|
|
path: cmake-build
|
|
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: |
|
|
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'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: hil-report-${{ matrix.display }}
|
|
path: ${{ env.HIL_REPORT_DIR }}/hil_report.md
|
|
if-no-files-found: ignore
|
|
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 ]
|
|
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
|
|
# ---------------------------------------
|
|
hil-hfp-iar:
|
|
needs: [ check-paths ]
|
|
if: |
|
|
needs.check-paths.outputs.code_changed == 'true' &&
|
|
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
|
|
env:
|
|
IAR_LMS_BEARER_TOKEN: ${{ secrets.IAR_LMS_BEARER_TOKEN }}
|
|
PYTHONUNBUFFERED: '1'
|
|
steps:
|
|
- name: Clean workspace
|
|
run: |
|
|
echo "Cleaning up previous run"
|
|
rm -rf "${{ github.workspace }}"
|
|
mkdir -p "${{ github.workspace }}"
|
|
|
|
- name: Toolchain version
|
|
run: |
|
|
iccarm --version
|
|
|
|
- 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/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: |
|
|
if [ -f hil_select.json ]; then
|
|
MATRIX_JSON=$(python test/hil/hil_ci_set_matrix.py --select "$(cat hil_select.json)" test/hil/hfp.json)
|
|
else
|
|
MATRIX_JSON=$(python test/hil/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'
|
|
run: |
|
|
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
|
|
done
|
|
|
|
- name: Test on actual hardware (hardware in the loop)
|
|
run: |
|
|
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)
|
|
python3 test/hil/hil_test.py $SEL_ARGS hfp.json
|
|
|
|
- name: Upload HIL report
|
|
if: always() && github.event_name == 'pull_request'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: hil-report-hfp-iar
|
|
path: hil_report.md
|
|
if-no-files-found: ignore
|
|
overwrite: true
|