99# └─ check-submodules verify all deps present
1010#
1111# Stage 2: BUILD (parallel, gated by Stage 1)
12- # ├─ build-emulator Docker image → artifact
13- # └─ build-arm-firmware cross-compile → .bin/.elf (downloadable)
12+ # ├─ build-emulator Docker image → artifact [matrix: full / bitcoin-only]
13+ # └─ build-arm-firmware cross-compile → .bin/.elf (downloadable) [same matrix]
1414#
1515# Stage 3: TEST (parallel, gated by Stage 2)
16- # ├─ unit-tests GoogleTest (make xunit)
17- # └─ python-integration full test suite
16+ # ├─ unit-tests GoogleTest (make xunit) [same matrix — proves each
17+ # │ variant's coin/token gating actually compiles+passes]
18+ # └─ python-integration full test suite (full/default variant only)
1819#
1920# Stage 4: PUBLISH (manual trigger, all tests must pass)
20- # └─ publish-emulator DockerHub push (workflow_dispatch only)
21+ # └─ publish-emulator DockerHub push, full/default variant only (workflow_dispatch only)
2122
2223name : CI
2324
99100
100101 static-analysis :
101102 runs-on : ubuntu-latest
102- timeout-minutes : 5
103+ timeout-minutes : 10
103104 steps :
104105 - name : Checkout
105106 uses : actions/checkout@v6
@@ -212,9 +213,22 @@ jobs:
212213 # ═══════════════════════════════════════════════════════════
213214
214215 build-emulator :
216+ name : build-emulator${{ matrix.label }}
215217 needs : [lint-format, static-analysis, check-submodules, secret-scan]
216218 runs-on : ubuntu-latest
217219 timeout-minutes : 15
220+ strategy :
221+ fail-fast : false
222+ matrix :
223+ include :
224+ # Regular/full includes every supported chain, including Zcash
225+ # shielded/Orchard. Bitcoin-only is the sole reduced build.
226+ - variant : full
227+ label : " "
228+ cmake_flags : " "
229+ - variant : bitcoin-only
230+ label : " (bitcoin-only)"
231+ cmake_flags : " -DKK_BITCOIN_ONLY=ON"
218232 steps :
219233 - name : Checkout
220234 uses : actions/checkout@v6
@@ -250,27 +264,39 @@ jobs:
250264 if : steps.cache-base.outputs.cache-hit == 'true'
251265 run : docker load -i /tmp/base-image.tar
252266
253- - name : Build emulator image
267+ - name : Build emulator image (${{ matrix.variant }})
254268 run : |
255269 docker build \
256- -t ${{ env.EMU_IMAGE }} \
270+ -t ${{ env.EMU_IMAGE }}-${{ matrix.variant }} \
271+ --build-arg coinsupport="${{ matrix.cmake_flags }}" \
257272 -f scripts/emulator/Dockerfile \
258273 .
259274
260275 - name : Save emulator image
261- run : docker save ${{ env.EMU_IMAGE }} -o /tmp/emu-image.tar
276+ run : docker save ${{ env.EMU_IMAGE }}-${{ matrix.variant }} -o /tmp/emu-image.tar
262277
263278 - name : Upload emulator image artifact
264279 uses : actions/upload-artifact@v7
265280 with :
266- name : emu-image
281+ name : emu-image-${{ matrix.variant }}
267282 path : /tmp/emu-image.tar
268283 retention-days : 1
269284
270285 build-arm-firmware :
286+ name : build-arm-firmware${{ matrix.label }}
271287 needs : [lint-format, static-analysis, check-submodules, secret-scan]
272288 runs-on : ubuntu-latest
273289 timeout-minutes : 15
290+ strategy :
291+ fail-fast : false
292+ matrix :
293+ include :
294+ - variant : full
295+ label : " "
296+ cmake_flags : " "
297+ - variant : bitcoin-only
298+ label : " (bitcoin-only)"
299+ cmake_flags : " -DKK_BITCOIN_ONLY=ON"
274300 steps :
275301 - name : Checkout
276302 uses : actions/checkout@v6
@@ -312,70 +338,102 @@ jobs:
312338 echo "git_short=${GIT_SHORT}" >> "$GITHUB_OUTPUT"
313339 echo "Firmware version: ${FW_VERSION} (${GIT_SHORT})"
314340
315- - name : Cross-compile firmware for ARM
341+ - name : Cross-compile firmware for ARM (${{ matrix.variant }})
316342 run : |
317343 docker run --rm \
318344 -v ${{ github.workspace }}:/root/keepkey-firmware:z \
319345 ${{ env.BASE_IMAGE }} /bin/sh -c "\
320346 mkdir /root/build && cd /root/build && \
321347 cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \
322348 -DCMAKE_BUILD_TYPE=MinSizeRel \
323- -DCMAKE_COLOR_MAKEFILE=ON && \
349+ -DCMAKE_COLOR_MAKEFILE=ON \
350+ ${{ matrix.cmake_flags }} && \
324351 make && \
325352 mkdir -p /root/keepkey-firmware/bin && \
326353 cp bin/*.bin /root/keepkey-firmware/bin/ && \
327354 cp bin/*.elf /root/keepkey-firmware/bin/ && \
355+ cp bin/*.map /root/keepkey-firmware/bin/ 2>/dev/null || true && \
356+ arm-none-eabi-size -A bin/firmware.keepkey.elf > /root/keepkey-firmware/bin/firmware.keepkey.size.txt 2>/dev/null || true && \
357+ find . -name '*.su' -print0 | tar czf /root/keepkey-firmware/bin/stack-usage.tgz --null -T - && \
328358 chmod -R a+rw /root/keepkey-firmware/bin"
329359
360+ # SRAM budget gate — RC7's privacy-enabled build hard-faulted on boot
361+ # because static SRAM left an 11.2 KB gap while msg_write() carried a
362+ # 12.4 KB stack frame. keepkey.ld now ASSERTs a 16 KiB reserve at link
363+ # time; this step reports the numbers and enforces the frame margin
364+ # (tools/sram-budgets.json).
365+ - name : SRAM budget gate (${{ matrix.variant }})
366+ run : |
367+ pip install --quiet pyelftools
368+ python3 tools/check_sram_budget.py \
369+ --elf bin/firmware.keepkey.elf \
370+ --su-tar bin/stack-usage.tgz \
371+ --budgets tools/sram-budgets.json \
372+ --variant "${{ matrix.variant }}"
373+
330374 - name : Rename firmware artifacts
331375 run : |
332376 cd bin
333377 for f in *.bin; do
334378 [ -f "$f" ] || continue
335- mv "$f" "firmware.keepkey.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${f}"
379+ mv "$f" "firmware.keepkey.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${{ matrix.variant }}-${ f}"
336380 done
337381 for f in *.elf; do
338382 [ -f "$f" ] || continue
339- mv "$f" "firmware.keepkey.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${f}"
383+ mv "$f" "firmware.keepkey.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${{ matrix.variant }}-${ f}"
340384 done
341385 ls -lh
342- echo "::notice::Firmware v${{ steps.version.outputs.fw_version }} built successfully"
386+ echo "::notice::Firmware v${{ steps.version.outputs.fw_version }} (${{ matrix.variant }}) built successfully"
343387
344388 - name : Upload firmware artifacts
345389 uses : actions/upload-artifact@v7
346390 with :
347- name : firmware-v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}
391+ name : firmware-v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${{ matrix.variant }}
348392 path : |
349393 bin/*.bin
350394 bin/*.elf
395+ bin/*.map
396+ bin/*.size.txt
397+ bin/stack-usage.tgz
351398 retention-days : 90
352399
353400 # ═══════════════════════════════════════════════════════════
354401 # STAGE 3: TEST — run only after builds succeed
355402 # ═══════════════════════════════════════════════════════════
356403
357404 unit-tests :
405+ name : unit-tests${{ matrix.label }}
358406 needs : build-emulator
359407 runs-on : ubuntu-latest
360408 timeout-minutes : 10
409+ strategy :
410+ fail-fast : false
411+ matrix :
412+ include :
413+ - variant : full
414+ label : " "
415+ cmake_flags : " "
416+ - variant : bitcoin-only
417+ label : " (bitcoin-only)"
418+ cmake_flags : " -DKK_BITCOIN_ONLY=ON"
361419 steps :
362420 - name : Download emulator image
363421 uses : actions/download-artifact@v8
364422 with :
365- name : emu-image
423+ name : emu-image-${{ matrix.variant }}
366424 path : /tmp
367425
368426 - name : Load emulator image
369427 run : docker load -i /tmp/emu-image.tar
370428
371- - name : Run unit tests
429+ - name : Run unit tests (${{ matrix.variant }})
372430 run : |
373431 # make xunit returns non-zero if any test fails — capture
374432 # exit code so JUnit XML still gets copied for reporting
375433 docker run --rm \
376434 -v ${{ github.workspace }}/test-reports:/kkemu/test-reports \
377435 --entrypoint /bin/sh \
378- ${{ env.EMU_IMAGE }} \
436+ ${{ env.EMU_IMAGE }}-${{ matrix.variant }} \
379437 -c "mkdir -p /kkemu/test-reports/firmware-unit && \
380438 make xunit; RC=\$?; \
381439 cp -r unittests/*.xml /kkemu/test-reports/firmware-unit/ 2>/dev/null; \
@@ -385,7 +443,7 @@ jobs:
385443 uses : actions/upload-artifact@v7
386444 if : always()
387445 with :
388- name : unit-test-results
446+ name : unit-test-results-${{ matrix.variant }}
389447 path : test-reports/firmware-unit/
390448 retention-days : 30
391449
@@ -684,7 +742,9 @@ jobs:
684742 uses : actions/download-artifact@v4
685743 continue-on-error : true
686744 with :
687- name : unit-test-results
745+ # Report covers the regular/full build. Bitcoin-only is built and
746+ # unit-tested in its own matrix leg but does not get a PDF.
747+ name : unit-test-results-full
688748 path : test-reports/firmware-unit/
689749
690750 - name : Download python test results
@@ -744,7 +804,8 @@ jobs:
744804 - name : Download emulator image
745805 uses : actions/download-artifact@v8
746806 with :
747- name : emu-image
807+ # Publish the regular/full image, including Zcash privacy support.
808+ name : emu-image-full
748809 path : /tmp
749810
750811 - name : Load emulator image
@@ -759,8 +820,8 @@ jobs:
759820
760821 - name : Tag images for publish
761822 run : |
762- docker tag ${{ env.EMU_IMAGE }} kktech/kkemu:latest
763- docker tag ${{ env.EMU_IMAGE }} kktech/kkemu:v${{ steps.version.outputs.fw_version }}
823+ docker tag ${{ env.EMU_IMAGE }}-full kktech/kkemu:latest
824+ docker tag ${{ env.EMU_IMAGE }}-full kktech/kkemu:v${{ steps.version.outputs.fw_version }}
764825
765826 - name : Login to DockerHub
766827 uses : docker/login-action@v4
0 commit comments