Skip to content

build-firmware

build-firmware #3

name: build-firmware
# Builds Zephyr's hci_uart sample for the nRF52840 USB Dongle and uploads
# the .hex as a GitHub Release asset. Triggered on:
# - manual dispatch (workflow_dispatch),
# - any tag matching `firmware-*` (the canonical way to publish).
#
# After a successful run, update src/bert/firmware/manifest.json with the
# release tag + SHA256 from the workflow output, commit, and cut a Bert
# release. Bert will then download this firmware on demand at flash time.
on:
workflow_dispatch:
inputs:
release_tag:
description: "Release tag to publish under (e.g. firmware-2026.05.06)"
required: true
zephyr_revision:
description: "Zephyr revision to build against"
required: true
default: "v3.7.0"
push:
tags:
- "firmware-*"
permissions:
contents: write # needed to create releases
defaults:
run:
shell: bash # GHA defaults to /bin/sh inside containers; we use bash idioms
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/zephyrproject-rtos/ci:v0.27.4
options: --user root
env:
ZEPHYR_REVISION: ${{ inputs.zephyr_revision || 'v3.7.0' }}
steps:
- name: Checkout Bert (only for context; not strictly needed to build hci_uart)
uses: actions/checkout@v4
- name: Set up Zephyr workspace
run: |
set -euxo pipefail
mkdir -p /workspace
cd /workspace
west init -m https://github.com/zephyrproject-rtos/zephyr --mr "$ZEPHYR_REVISION" .
west update --narrow -o=--depth=1
west zephyr-export
pip install -r zephyr/scripts/requirements.txt
- name: Locate Zephyr SDK
run: |
set -euxo pipefail
# The CI image bundles a Zephyr SDK under /opt/toolchains; CMake
# finds it via ZEPHYR_SDK_INSTALL_DIR. Discover whichever version
# the image ships, prefer the highest if multiple are present.
SDK_DIR=$(ls -d /opt/toolchains/zephyr-sdk-* 2>/dev/null | sort -V | tail -1 || true)
if [ -z "$SDK_DIR" ]; then
echo "::error::no Zephyr SDK found under /opt/toolchains; image may have changed"
ls -la /opt/toolchains 2>&1 || true
exit 1
fi
echo "Using Zephyr SDK at $SDK_DIR"
echo "ZEPHYR_SDK_INSTALL_DIR=$SDK_DIR" >> "$GITHUB_ENV"
- name: Build hci_uart for nRF52840 USB Dongle
working-directory: /workspace
run: |
set -euxo pipefail
west build \
-b nrf52840dongle/nrf52840 \
-d build-hci-dongle \
zephyr/samples/bluetooth/hci_uart
mkdir -p $GITHUB_WORKSPACE/firmware-out
cp build-hci-dongle/zephyr/zephyr.hex \
$GITHUB_WORKSPACE/firmware-out/hci_uart_nrf52840dongle.hex
- name: Hash + size
id: meta
working-directory: ${{ github.workspace }}/firmware-out
run: |
set -euxo pipefail
SHA=$(sha256sum hci_uart_nrf52840dongle.hex | awk '{print $1}')
SIZE=$(stat -c%s hci_uart_nrf52840dongle.hex 2>/dev/null || stat -f%z hci_uart_nrf52840dongle.hex)
echo "sha256=$SHA" >> $GITHUB_OUTPUT
echo "size=$SIZE" >> $GITHUB_OUTPUT
echo "## hci_uart_nrf52840dongle.hex" >> $GITHUB_STEP_SUMMARY
echo "- sha256: \`$SHA\`" >> $GITHUB_STEP_SUMMARY
echo "- size: $SIZE bytes" >> $GITHUB_STEP_SUMMARY
echo "- zephyr: $ZEPHYR_REVISION" >> $GITHUB_STEP_SUMMARY
- name: Upload build artefact (always)
uses: actions/upload-artifact@v4
with:
name: hci_uart_nrf52840dongle
path: firmware-out/hci_uart_nrf52840dongle.hex
if-no-files-found: error
- name: Determine release tag
id: tag
run: |
if [ -n "${{ inputs.release_tag }}" ]; then
echo "tag=${{ inputs.release_tag }}" >> $GITHUB_OUTPUT
else
echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
fi
- name: Create / update release
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.tag }}
files: firmware-out/hci_uart_nrf52840dongle.hex
fail_on_unmatched_files: true
body: |
Pre-built firmware images for Bert.
| file | sha256 | size | zephyr |
|---|---|---|---|
| `hci_uart_nrf52840dongle.hex` | `${{ steps.meta.outputs.sha256 }}` | ${{ steps.meta.outputs.size }} bytes | `${{ env.ZEPHYR_REVISION }}` |
Update `src/bert/firmware/manifest.json` to point at this release:
```json
"release_tag": "${{ steps.tag.outputs.tag }}",
"base_url": "https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}",
"firmware": {
"hci": {
"filename": "hci_uart_nrf52840dongle.hex",
"sha256": "${{ steps.meta.outputs.sha256 }}",
"size_bytes": ${{ steps.meta.outputs.size }}
}
}
```