1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
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
example-map:
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:
# 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
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: Resolve PR example filter
if: inputs.example-map != '' && inputs.example-map != '{}'
env:
# values are PR-derived - keep them out of ${{ }} script interpolation
# (env expansion word-splits but never re-parses shell metacharacters)
EXAMPLE_MAP: ${{ inputs.example-map }}
FAMILY: ${{ matrix.arg }}
run: |
# -e flags for this family; a family absent from the map builds everything
EX_ARGS=$(printf '%s' "$EXAMPLE_MAP" | jq -r --arg fam "$FAMILY" '(.[$fam] // []) | map("-e " + .) | join(" ")') || EX_ARGS=''
# the map's values are example dir names from the PR checkout, and `jq -r`
# un-escapes them: a path with a newline (git allows it) would otherwise write
# extra NAME=VALUE lines into GITHUB_ENV for every later step of this job.
# Anything outside the example-name alphabet drops the filter (= build all),
# which is the safe direction.
case "$EX_ARGS" in
*[!-A-Za-z0-9_/\ ]*)
echo "::warning::unexpected characters in the example filter - building all examples"
EX_ARGS='' ;;
esac
echo "EX_ARGS=$EX_ARGS"
echo "EX_ARGS=$EX_ARGS" >> $GITHUB_ENV
- 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 }} $EX_ARGS
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 }} $EX_ARGS
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
# $EX_ARGS is passed for the BOARD it picks, not to scope the targets:
# --one-first now chooses a board that can build the -e set (tools/build.py),
# so omitting it here would configure a DIFFERENT, empty build dir and upload
# --identical for a board that was never compiled. The target list is not
# scoped by it - `examples-membrowse-upload` is not `all`, so it passes
# through as the aggregate, which has no DEPENDS (hw/bsp/family_support.cmake):
# it rebuilds nothing and still records every example, --identical for the
# ones without an elf.
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 }} $EX_ARGS
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
cmake-build/cmake-build-*/metrics_by_example.json
- name: Artifact name
if: inputs.upload-artifacts == true
env:
ARG: ${{ matrix.arg }}
run: |
# -e example filters carry '/', which upload-artifact forbids in artifact
# names; strip them from the NAME only (the build already consumed them).
# Names without -e stay byte-identical to before. Two entries differing
# only in their -e list cannot exist - the -e list is a function of
# (board), and variant suffixes (--build-name/-D/--cflag) survive the
# strip - so the stripped name is still unique per matrix entry.
TAG=$(printf '%s' "$ARG" | sed -E 's/ -e [^ ]+//g')
# board and example names come from the roster, which a PR can edit; a newline
# in one would write extra NAME=VALUE lines into GITHUB_ENV for every later
# step. There is no safe fallback name here - a wrong one mislabels the
# firmware the rig then flashes - so refuse instead.
case "$TAG" in
*[!-A-Za-z0-9_/\ .=+]*)
echo "::error::refusing to build an artifact name from '$ARG'"; exit 1 ;;
esac
echo "ARTIFACT_TAG=$TAG" >> $GITHUB_ENV
- 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 }}-${{ env.ARTIFACT_TAG }}
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
|