Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 80 additions & 15 deletions .github/workflows/build-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ name: Build app
on:
workflow_call:
inputs:
board:
description: West board target, e.g. nrf54l15dk/nrf54l15/cpuapp.
type: string
required: true
board-id:
description: >-
Board identifier used in artifact/release names, <board>-<chip>
(e.g. nrf54l15dk-nrf54l15).
type: string
required: true
sysbuild:
description: >-
Build with sysbuild (multi-image) instead of a single plain image.
Required for nRF54H20, whose Bluetooth controller runs on the radio
core (cpurad) as a separate image reached over HCI/IPC.
type: boolean
required: false
default: false
version:
description: >-
Release version (vX.Y.Z[-suffix]). When set, app/VERSION is
Expand All @@ -12,11 +30,6 @@ on:
type: string
required: false
default: ""
artifact-name:
description: Name of the uploaded hex+elf artifact.
type: string
required: false
default: quickstart-bluetooth-nrf54l15dk
secrets:
MEMFAULT_PROJECT_KEY:
description: Memfault project key baked into the build via CONFIG_MEMFAULT_NCS_PROJECT_KEY.
Expand Down Expand Up @@ -67,26 +80,69 @@ jobs:
- name: West zephyr-export
run: west zephyr-export

- name: Build for nrf54l15dk/nrf54l15/cpuapp
# --no-sysbuild: plain app image, no MCUboot/sysbuild.
- name: Build for ${{ inputs.board }}
# L-series (nrf54l15/nrf54lm20) are single-core: --no-sysbuild yields one
# plain image. nRF54H20 needs sysbuild so the ipc_radio controller image
# (cpurad) and radio loader are built alongside the app image.
run: |
west build -b nrf54l15dk/nrf54l15/cpuapp --no-sysbuild \
set -euo pipefail
if [ "${{ inputs.sysbuild }}" = "true" ]; then
SYSBUILD_FLAG=--sysbuild
else
SYSBUILD_FLAG=--no-sysbuild
fi
west build -b ${{ inputs.board }} $SYSBUILD_FLAG \
project/app \
-- \
-DCONFIG_MEMFAULT_NCS_PROJECT_KEY=\"${{ secrets.MEMFAULT_PROJECT_KEY }}\"

- name: Resolve the app image build directory
id: appdir
# With sysbuild the primary app image lands in build/app; a plain build
# puts it directly in build. The .elf/.config we care about (Memfault
# instrumentation lives in the app image) come from here.
run: |
if [ "${{ inputs.sysbuild }}" = "true" ]; then
echo "dir=build/app" >> "$GITHUB_OUTPUT"
else
echo "dir=build" >> "$GITHUB_OUTPUT"
fi

- name: Read Memfault software version/type from the build config
id: fwver
# The exact strings the firmware reports, so Memfault symbol uploads link
# to the right software version (no guessing the Zephyr format).
run: |
CONF=build/zephyr/.config
CONF=${{ steps.appdir.outputs.dir }}/zephyr/.config
VER=$(sed -nE 's/^CONFIG_MEMFAULT_NCS_FW_VERSION="(.*)"$/\1/p' "$CONF")
TYPE=$(sed -nE 's/^CONFIG_MEMFAULT_NCS_FW_TYPE="(.*)"$/\1/p' "$CONF")
echo "fw_version=$VER" >> "$GITHUB_OUTPUT"
echo "fw_type=$TYPE" >> "$GITHUB_OUTPUT"
echo "Memfault software: type=$TYPE version=$VER"

- name: Assemble the flashable hex
id: hex
# Single deliverable per board. nRF54H20 sysbuild emits one hex per
# domain (app + ipc_radio + radio_loader + UICR/BICR/periphconf); merge
# them into a single flashable image so every board ships one .hex, like
# the L-series plain build. mergehex.py is bundled with Zephyr (no
# dependency on external command-line tools).
run: |
set -euo pipefail
if [ "${{ inputs.sysbuild }}" = "true" ]; then
python3 zephyr/scripts/build/mergehex.py \
-o build/merged.hex \
build/app/zephyr/zephyr.hex \
build/ipc_radio/zephyr/zephyr.hex \
build/radio_loader/zephyr/zephyr.hex \
build/uicr/zephyr/uicr.hex \
build/uicr/zephyr/periphconf.hex \
build/app/zephyr/bicr.hex
echo "path=build/merged.hex" >> "$GITHUB_OUTPUT"
else
echo "path=build/zephyr/zephyr.hex" >> "$GITHUB_OUTPUT"
fi

- name: Install SBOM (west ncs-sbom) dependencies
# scancode-toolkit (one of the default license detectors) needs these
# system libs on top of its pip deps. See:
Expand All @@ -98,17 +154,26 @@ jobs:
pip3 install --quiet -r nrf/scripts/requirements-west-ncs-sbom.txt

- name: Generate SPDX SBOM
# ncs-sbom is sysbuild-aware: given the top-level build dir it reads
# domains.yaml and covers every image.
run: |
west ncs-sbom -d build \
--package-supplier "Nordic Semiconductor ASA" \
--output-spdx build/zephyr/quickstart-bluetooth.spdx
--output-spdx build/quickstart-bluetooth.spdx

- name: Stage build artifacts with canonical names
# Normalize names so downstream (release) handling is board-agnostic:
# the H20 merged hex and the L-series zephyr.hex both become zephyr.hex.
run: |
set -euo pipefail
mkdir -p out
cp "${{ steps.hex.outputs.path }}" out/zephyr.hex
cp "${{ steps.appdir.outputs.dir }}/zephyr/zephyr.elf" out/zephyr.elf
cp build/quickstart-bluetooth.spdx out/quickstart-bluetooth.spdx

- name: Upload build artifacts (hex + elf + spdx)
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact-name }}
path: |
build/zephyr/zephyr.hex
build/zephyr/zephyr.elf
build/zephyr/quickstart-bluetooth.spdx
name: quickstart-bluetooth-${{ inputs.board-id }}
path: out/
if-no-files-found: error
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,27 @@ permissions:

jobs:
build:
strategy:
fail-fast: false
# Supported boards. Keep in sync with the matrix in release.yml.
# board-id (<board>-<chip>) is used for artifact/release file names.
# sysbuild=true for nRF54H20 (multi-core: BLE controller on cpurad).
matrix:
include:
- board: nrf54l15dk/nrf54l15/cpuapp
board-id: nrf54l15dk-nrf54l15
sysbuild: false
- board: nrf54lm20dk/nrf54lm20a/cpuapp
board-id: nrf54lm20dk-nrf54lm20a
sysbuild: false
- board: nrf54h20dk/nrf54h20/cpuapp
board-id: nrf54h20dk-nrf54h20
sysbuild: true
uses: ./.github/workflows/build-app.yml
with:
board: ${{ matrix.board }}
board-id: ${{ matrix.board-id }}
sysbuild: ${{ matrix.sysbuild }}
secrets:
MEMFAULT_PROJECT_KEY: ${{ secrets.MEMFAULT_PROJECT_KEY }}

Expand Down
103 changes: 76 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
# Manually dispatched. Given a release version string it:
# 1. validates the version format and derives the git tag + Memfault software
# version (scripts/app_version.py);
# 2. builds the firmware with app/VERSION set to that version (build-app.yml),
# so the device reports the release version to Memfault, and generates the
# SPDX SBOM (west ncs-sbom) alongside zephyr.hex + zephyr.elf;
# 3. renames those three build outputs to include the version + board/chip
# (matching the Asset Tracker Template release naming, e.g.
# 2. builds the firmware for every supported board with app/VERSION set to that
# version (build-app.yml), so each device reports the release version to
# Memfault, and generates the SPDX SBOM (west ncs-sbom) alongside
# zephyr.hex + zephyr.elf;
# 3. renames each board's three build outputs to include the version +
# board/chip (matching the Asset Tracker Template release naming, e.g.
# quickstart-bluetooth-v1.2.3-nrf54l15dk-nrf54l15.hex) — no zip;
# 4. uploads zephyr.elf as symbols to the Memfault "quickstart-shared-project"
# (registers the software version for symbolication);
# 5. tags the target commit and creates a GitHub release with the renamed
# .hex, .elf, and .spdx attached directly.
# 4. uploads each board's zephyr.elf as symbols to the Memfault project
# (registers the software version for symbolication; every board has its
# own GNU Build ID);
# 5. tags the target commit and creates a single GitHub release with all
# boards' renamed .hex, .elf, and .spdx attached directly.
#
# The version input is the single knob — do NOT hardcode versions elsewhere; the
# committed app/VERSION stays at 0.0.0-dev and is overwritten at build time.
Expand Down Expand Up @@ -73,17 +75,45 @@

build:
needs: validate
strategy:
fail-fast: false
# Supported boards. Keep in sync with the matrix in build.yml and the
# publish job below. sysbuild=true for nRF54H20 (BLE controller on cpurad).
matrix:
include:
- board: nrf54l15dk/nrf54l15/cpuapp
board-id: nrf54l15dk-nrf54l15
sysbuild: false
- board: nrf54lm20dk/nrf54lm20a/cpuapp
board-id: nrf54lm20dk-nrf54lm20a
sysbuild: false
- board: nrf54h20dk/nrf54h20/cpuapp
board-id: nrf54h20dk-nrf54h20
sysbuild: true
uses: ./.github/workflows/build-app.yml
with:
version: ${{ inputs.version }}
board: ${{ matrix.board }}
board-id: ${{ matrix.board-id }}
sysbuild: ${{ matrix.sysbuild }}
secrets:
MEMFAULT_PROJECT_KEY: ${{ secrets.MEMFAULT_PROJECT_KEY }}

# Per-board: rename the build outputs, upload that board's symbols to Memfault,
# and stage the renamed files as a workflow artifact for the release job.
publish:
needs: [validate, build]
strategy:
fail-fast: false
# Keep in sync with the build matrix above.
matrix:
include:
- board-id: nrf54l15dk-nrf54l15
- board-id: nrf54lm20dk-nrf54lm20a
- board-id: nrf54h20dk-nrf54h20
runs-on: ubuntu-24.04
permissions:
contents: write # tag push + release creation
contents: read
defaults:
run:
shell: bash
Expand All @@ -96,7 +126,7 @@
- name: Download build artifacts (hex + elf + spdx)
uses: actions/download-artifact@v8
with:
name: quickstart-bluetooth-nrf54l15dk
name: quickstart-bluetooth-${{ matrix.board-id }}
path: artifacts

- name: Locate build outputs
Expand All @@ -114,29 +144,29 @@
- name: Rename release files (version + board/chip)
# Matches the Asset Tracker Template release naming convention:
# <app>-<tag>-<board>-<chip>.<ext> — attached directly, no zip.
# matrix.board-id is already <board>-<chip>.
id: release_files
run: |
set -euo pipefail
BASENAME="quickstart-bluetooth-${{ needs.validate.outputs.tag }}-nrf54l15dk-nrf54l15"
BASENAME="quickstart-bluetooth-${{ needs.validate.outputs.tag }}-${{ matrix.board-id }}"
mkdir -p release
cp "${{ steps.files.outputs.hex }}" "release/${BASENAME}.hex"
cp "${{ steps.files.outputs.elf }}" "release/${BASENAME}.elf"
cp "${{ steps.files.outputs.spdx }}" "release/${BASENAME}.spdx"
echo "hex=release/${BASENAME}.hex" >> "$GITHUB_OUTPUT"
echo "elf=release/${BASENAME}.elf" >> "$GITHUB_OUTPUT"
echo "spdx=release/${BASENAME}.spdx" >> "$GITHUB_OUTPUT"

- name: Upload release files as workflow artifacts (always, for inspection)
- name: Upload renamed release files as a workflow artifact
# Collected by the release job (and available for inspection on dry runs).
uses: actions/upload-artifact@v7
with:
name: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files
name: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files-${{ matrix.board-id }}
path: release/
if-no-files-found: error

- name: Upload symbols to Memfault
# Software type/version come from the build config so the symbol file is
# linked to the version the device reports. --check-uploaded skips
# symbols already uploaded (idempotent re-runs).
# linked to the version the device reports. Each board's ELF has its own
# GNU Build ID, so all three are uploaded under the same software
# version. --check-uploaded skips symbols already uploaded (idempotent).
env:
MEMFAULT_ORG_TOKEN: ${{ secrets.MEMFAULT_ORG_TOKEN }}
MEMFAULT_ORG_SLUG: ${{ secrets.MEMFAULT_ORG_SLUG }}
Expand All @@ -154,18 +184,32 @@
--check-uploaded \
"${{ steps.files.outputs.elf }}"

# Single GitHub release with every board's renamed files attached.
release:
needs: [validate, publish]
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-24.04
permissions:
contents: write # tag push + release creation
defaults:
run:
shell: bash
steps:
- name: Download all boards' renamed release files
uses: actions/download-artifact@v8
with:
pattern: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files-*
path: release
merge-multiple: true

- name: Create or update GitHub release (also creates the tag)
if: ${{ !inputs.dry_run }}
# gh release create makes the tag via the Releases API. Unlike git push,
# that path is not blocked by the GITHUB_TOKEN workflow-file restriction,
# so it works even when the target commit's workflows differ from main.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }} # gh has no checkout to infer the repo from
TAG: ${{ needs.validate.outputs.tag }}
HEX: ${{ steps.release_files.outputs.hex }}
ELF: ${{ steps.release_files.outputs.elf }}
SPDX: ${{ steps.release_files.outputs.spdx }}
TARGET_SHA: ${{ inputs.sha != '' && inputs.sha || github.sha }}
run: |
set -euo pipefail
Expand All @@ -175,14 +219,19 @@
# Idempotent: create if missing, else re-attach the files (--clobber) so
# re-running an existing version refreshes the assets instead of failing.
if gh release view "$TAG" >/dev/null 2>&1; then
gh release upload "$TAG" "$HEX" "$ELF" "$SPDX" --clobber
gh release upload "$TAG" release/* --clobber
else
gh release create "$TAG" "$HEX" "$ELF" "$SPDX" --target "$TARGET_SHA" \
gh release create "$TAG" release/* --target "$TARGET_SHA" \
--title "$TAG" --generate-notes $PRERELEASE
fi

dry-run-summary:
needs: [validate, publish]
if: ${{ inputs.dry_run }}
runs-on: ubuntu-24.04
steps:
- name: Dry-run summary
if: ${{ inputs.dry_run }}
run: |
echo "DRY RUN: skipped git tag and GitHub release for ${{ needs.validate.outputs.tag }}."
echo "Built ${{ steps.release_files.outputs.hex }}, ${{ steps.release_files.outputs.elf }}, ${{ steps.release_files.outputs.spdx }} and uploaded symbols to Memfault."
echo "Built and renamed all boards' files and uploaded symbols to Memfault."
echo "Renamed release files are attached as workflow artifacts for inspection."
Comment on lines +229 to +237
Loading
Loading