Files
tinyusb/.github/workflows/build_util.yml
hathach 9b3259e60f hil: controller-aware scheduling of flash and usbtest concurrency
Full-fleet profiling (HIL_PROFILE=1 instrumentation, included) showed each
uPD720201 controller's serialized usbtest battery chain dominates wall time,
and a board whose marginal device port bounces during concurrent batteries
can wedge or kill the controller ("xHCI host not responding to stop endpoint
command"). Every such death traced to mimxrt1015's port (its old "kills the
uPD720201" reputation) - it is removed from the config until recabled;
mimxrt1064's enum-retry stalls were a loose device cable (re-seated).
nrf54lm20dk moves to boards-skip until its failing J-Link probe is replugged.
With the hardware fixed both cards run width-4 batteries plus full flash
churn clean, so scheduling stays simple: two symmetric knobs, flashes and
batteries budgeted per controller.

- schedule_boards(): dispatch boards round-robin across host controllers from
  a persisted hint cache (~/.cache/tinyusb-hil/ctrl_cache.json), learned and
  merge-on-write refreshed each run (concurrent HIL jobs keep each other's
  entries). Only the cached PCI address is consumed - dispatch order and
  first-flash budgeting, never battery serialization (batteries resolve live
  or fail closed to an all-slot permit).
- HIL_FLASH_PARALLEL (8) and HIL_USBTEST_PARALLEL (4) are budgeted per
  controller via lock slots assigned on first sight.
- re-runs: a failed run writes <report dir>/<config>.failed with the exact
  re-run spec (--accumulate -b <failed board> -bt <board>:<its failed
  tests>) instead of the inverted --skip-board list of everything that
  passed; --skip-board is gone, --flasher/--exclude-flasher scope a config
  across CI jobs by flasher type (no board names hardcoded in workflows),
  and -a/--accumulate merges a re-run into the existing report. The spec is
  stamped with GITHUB_RUN_ID and cleared on fresh runs, so a retry can never
  consume a spec left behind by a different run's dead or skipped attempt.
- CI: esp-idf firmware builds move out of hil-build into hil-build-esp, and
  the esptool-flashed boards run in their own hil-tinyusb-esp job, so the
  main hil-tinyusb run starts as soon as the fast toolchains finish instead
  of waiting on the slow esp-idf build (an esp toolchain flake previously
  skipped the whole rig run). Artifacts are namespaced per toolchain so the
  esp job downloads only esp-idf binaries.
- HIL_PROFILE=1: timestamped log lines, per-flash durations, permit-wait
  logging, uid->controller map dump for analysis.
- hil_report: per-variant test duration as a dedicated trailing column,
  recorded only by full runs.

Validated on the ci rig (fixed seeds 20260716/777, full fleet at 8/4):
738s/780s walls with only known-flake failures and no controller deaths,
vs 1134-1211s serialized-battery baseline.
2026-07-17 11:54:14 +07:00

112 lines
3.7 KiB
YAML

name: Reusable build util
on:
workflow_call:
inputs:
os:
required: false
type: string
default: 'ubuntu-latest'
build-system:
required: true
type: string
toolchain:
required: true
type: string
build-args:
required: true
type: string
build-options:
required: false
default: ''
type: string
upload-artifacts:
required: false
default: false
type: boolean
upload-metrics:
required: false
default: false
type: boolean
upload-membrowse:
required: false
default: false
type: boolean
code-changed:
required: false
default: true
type: boolean
jobs:
family:
runs-on: ${{ inputs.os }}
strategy:
fail-fast: false
matrix:
arg: ${{ fromJSON(inputs.build-args) }}
steps:
- name: Checkout TinyUSB
uses: actions/checkout@v6
with:
fetch-depth: ${{ !inputs.upload-membrowse && 1 || 0 }}
- name: Setup Toolchain
id: setup-toolchain
uses: ./.github/actions/setup_toolchain
with:
toolchain: ${{ inputs.toolchain }}
- name: Get Dependencies
uses: ./.github/actions/get_deps
with:
arg: ${{ matrix.arg }}
- name: Build
if: ${{ inputs.code-changed }}
env:
IAR_LMS_BEARER_TOKEN: ${{ secrets.IAR_LMS_BEARER_TOKEN }}
run: |
if [ "${{ inputs.toolchain }}" == "esp-idf" ]; then
docker run --rm -e MEMBROWSE_API_KEY="$MEMBROWSE_API_KEY" -e CI="$CI" -v $PWD:/project -w /project espressif/idf:tinyusb python tools/build.py --target all ${{ matrix.arg }}
else
BUILD_PY_ARGS="-s ${{ inputs.build-system }} ${{ steps.setup-toolchain.outputs.build_option }} ${{ inputs.build-options }} --target all"
if [ "${{ inputs.upload-metrics }}" = "true" ]; then
BUILD_PY_ARGS="$BUILD_PY_ARGS --target tinyusb_metrics"
fi
python tools/build.py $BUILD_PY_ARGS ${{ matrix.arg }}
fi
shell: bash
- name: Membrowse Upload
if: inputs.toolchain != 'esp-idf' && inputs.upload-membrowse == true
continue-on-error: true
env:
MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
run: |
# if code-changed is false --> there is no elf -> membrowse target upload with --identical flag
BUILD_PY_ARGS="-s ${{ inputs.build-system }} ${{ steps.setup-toolchain.outputs.build_option }} ${{ inputs.build-options }}"
python tools/build.py $BUILD_PY_ARGS --target examples-membrowse-upload -j 1 ${{ matrix.arg }}
shell: bash
- name: Upload Artifacts for Metrics
if: inputs.upload-metrics == true && inputs.code-changed == true
uses: actions/upload-artifact@v7
with:
name: metrics-${{ matrix.arg }}
path: cmake-build/cmake-build-*/metrics.json
- name: Upload Artifacts for Hardware Testing
if: inputs.upload-artifacts == true && inputs.code-changed == true
uses: actions/upload-artifact@v7
with:
name: binaries-${{ inputs.toolchain }}-${{ matrix.arg }}
path: |
cmake-build/cmake-build-*/*/*/*.elf
cmake-build/cmake-build-*/*/*/*.bin
cmake-build/cmake-build-*/*/*/*.bin
cmake-build/cmake-build-*/*/*/bootloader/bootloader.bin
cmake-build/cmake-build-*/*/*/partition_table/partition-table.bin
cmake-build/cmake-build-*/*/*/config.env
cmake-build/cmake-build-*/*/*/flash_args
cmake-build/hw/mcu/**/*.ld