summaryrefslogtreecommitdiff
path: root/.github/workflows/build_util.yml
blob: 52999616d4aefdbdacf2733085a696a5f6aea335 (plain)
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