Skip to content

Commit 5b7e9e0

Browse files
committed
.github: workflows: build once, test and release on main
Adds a CI orchestrator that resolves the next semantic version, builds firmware once, runs hardware tests against those prebuilt artifacts, and cuts a GitHub release when the commits warrant a version bump. Also switches gitlint to a conventional-commit + Zephyr subsystem title format, with a local `smha_commit_rules.py` that reuses the upstream Zephyr rules, and documents the new CI/release/commit flow in `doc/ci-and-contribution.md`. Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
1 parent 4342c23 commit 5b7e9e0

18 files changed

Lines changed: 678 additions & 59 deletions

File tree

.github/test/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ tests:
4444
fleet_sampling_configuration_var: MEMFAULT_FLEET_SAMPLING_CONFIGURATION
4545
cohort: ci-91m1-fota-nrf54l15
4646
cohort_name: CI 91m1 FOTA nRF54L15
47-
baseline_version: "1.0"
48-
update_version: "1.1"
47+
baseline_version: "1.0.0"
48+
update_version: "1.1.0"

.github/workflows/build.yml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
name: Build
22

33
on:
4-
push:
5-
branches:
6-
- main
4+
workflow_call:
5+
inputs:
6+
firmware_version:
7+
description: Semver written to each application VERSION file before building
8+
required: false
9+
type: string
10+
secrets: inherit
11+
workflow_dispatch:
12+
inputs:
13+
firmware_version:
14+
description: Semver written to each application VERSION file before building
15+
required: false
16+
type: string
717
pull_request:
818

919
permissions:
@@ -51,9 +61,24 @@ jobs:
5161
with:
5262
path: serial-modem-host-applications
5363

64+
- name: Set application VERSION files
65+
if: inputs.firmware_version != ''
66+
working-directory: serial-modem-host-applications
67+
run: |
68+
python3 scripts/ci/write_version.py \
69+
"${{ inputs.firmware_version }}" \
70+
applications
71+
5472
- name: Initialize West workspace
5573
uses: ./serial-modem-host-applications/.github/actions/west-init
5674

5775
- name: Build application
5876
working-directory: serial-modem-host-applications/applications/${{ matrix.app }}
5977
run: west build -b ${{ matrix.board }} -p
78+
79+
- name: Upload firmware artifact
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: firmware-${{ matrix.app }}
83+
path: serial-modem-host-applications/applications/${{ matrix.app }}/build/
84+
retention-days: 14

.github/workflows/ci.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Push-to-main CI orchestrator.
2+
#
3+
# Flow: resolve semver → build firmware → hardware test (prebuilt) → release on bump.
4+
# Also runs SonarCloud and Markdown link check in parallel (path-filtered).
5+
#
6+
# Individual workflows remain independently triggerable via workflow_dispatch.
7+
8+
name: CI
9+
10+
on:
11+
push:
12+
branches:
13+
- main
14+
15+
permissions:
16+
contents: write
17+
pull-requests: read
18+
19+
concurrency:
20+
group: ci-main-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
changes:
25+
runs-on: ubuntu-latest
26+
outputs:
27+
sonar: ${{ steps.filter.outputs.sonar }}
28+
markdown: ${{ steps.filter.outputs.markdown }}
29+
steps:
30+
- uses: actions/checkout@v6
31+
- uses: dorny/paths-filter@v3
32+
id: filter
33+
with:
34+
filters: |
35+
sonar:
36+
- '**/*.c'
37+
- '**/*.h'
38+
- .github/workflows/sonarcloud.yml
39+
- .github/workflows/ci.yml
40+
- sonar-project.properties
41+
- west.yml
42+
- '**/CMakeLists.txt'
43+
- '**/Kconfig*'
44+
- '**/prj.conf'
45+
markdown:
46+
- '**.md'
47+
48+
version:
49+
runs-on: ubuntu-latest
50+
outputs:
51+
version: ${{ steps.semver.outputs.version }}
52+
is_bump: ${{ steps.semver.outputs.changed }}
53+
steps:
54+
- uses: actions/checkout@v6
55+
with:
56+
fetch-depth: 0
57+
58+
- id: semver
59+
uses: PaulHatch/semantic-version@v5.4.0
60+
with:
61+
tag_prefix: v
62+
major_pattern: BREAKING CHANGE
63+
minor_pattern: feat
64+
patch_pattern: fix
65+
66+
build:
67+
needs: version
68+
uses: ./.github/workflows/build.yml
69+
with:
70+
firmware_version: ${{ needs.version.outputs.version }}
71+
secrets: inherit
72+
73+
sonarcloud:
74+
needs: changes
75+
if: needs.changes.outputs.sonar == 'true'
76+
uses: ./.github/workflows/sonarcloud.yml
77+
secrets: inherit
78+
79+
linkcheck:
80+
needs: changes
81+
if: needs.changes.outputs.markdown == 'true'
82+
uses: ./.github/workflows/markdown-link-checker.yml
83+
secrets: inherit
84+
85+
test:
86+
needs: build
87+
uses: ./.github/workflows/test.yml
88+
with:
89+
test: all
90+
use_prebuilt_firmware: true
91+
secrets: inherit
92+
93+
release:
94+
needs: [version, build, test, sonarcloud, linkcheck]
95+
if: >-
96+
always() &&
97+
needs.version.outputs.is_bump == 'true' &&
98+
needs.build.result == 'success' &&
99+
needs.test.result == 'success' &&
100+
(needs.sonarcloud.result == 'success' || needs.sonarcloud.result == 'skipped') &&
101+
(needs.linkcheck.result == 'success' || needs.linkcheck.result == 'skipped')
102+
uses: ./.github/workflows/release.yml
103+
with:
104+
version: ${{ needs.version.outputs.version }}
105+
secrets: inherit

.github/workflows/markdown-link-checker.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
name: Markdown link check
33

44
on:
5+
workflow_call:
6+
secrets: inherit
7+
workflow_dispatch:
58
pull_request:
69
paths:
710
- '**.md'
8-
push:
9-
branches:
10-
- main
11-
paths:
12-
- '**.md'
1311

1412
permissions:
1513
contents: read

.github/workflows/release.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Release
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
version:
7+
description: Release version without v prefix (e.g. 1.2.3)
8+
required: true
9+
type: string
10+
secrets: inherit
11+
workflow_dispatch:
12+
inputs:
13+
version:
14+
description: Release version without v prefix (e.g. 1.2.3)
15+
required: true
16+
type: string
17+
18+
permissions:
19+
contents: write
20+
21+
jobs:
22+
release:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v6
27+
with:
28+
fetch-depth: 0
29+
30+
- name: Download firmware artifacts
31+
uses: actions/download-artifact@v4
32+
with:
33+
pattern: firmware-*
34+
merge-multiple: true
35+
path: firmware
36+
37+
- name: Prepare release assets
38+
env:
39+
VERSION: ${{ inputs.version }}
40+
run: |
41+
set -eu
42+
mkdir -p release-assets
43+
for app in 91m1_ppp 93m1_ppp 93m1_at; do
44+
signed="firmware/${app}/zephyr/zephyr.signed.bin"
45+
elf="firmware/${app}/zephyr/zephyr.elf"
46+
if [ -f "$signed" ]; then
47+
cp "$signed" "release-assets/${app}-v${VERSION}.signed.bin"
48+
fi
49+
if [ -f "$elf" ]; then
50+
cp "$elf" "release-assets/${app}-v${VERSION}.elf"
51+
fi
52+
done
53+
ls -la release-assets/
54+
55+
- name: Create release
56+
uses: softprops/action-gh-release@v2
57+
with:
58+
tag_name: v${{ inputs.version }}
59+
name: v${{ inputs.version }}
60+
generate_release_notes: true
61+
files: release-assets/*
62+
63+
- name: Update VERSION files in repository
64+
env:
65+
VERSION: ${{ inputs.version }}
66+
run: |
67+
python3 scripts/ci/write_version.py "$VERSION" applications
68+
69+
- name: Commit VERSION bump
70+
env:
71+
VERSION: ${{ inputs.version }}
72+
run: |
73+
set -eu
74+
git config user.name "github-actions[bot]"
75+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
76+
git add applications/*/VERSION
77+
if git diff --staged --quiet; then
78+
echo "VERSION files already match v${VERSION}"
79+
exit 0
80+
fi
81+
git commit -m "ci: release: bump VERSION to v${VERSION} [skip ci]"
82+
git push

.github/workflows/sonarcloud.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: Sonarcloud
22

33
on:
4-
push:
5-
branches:
6-
- main
4+
workflow_call:
5+
secrets: inherit
6+
workflow_dispatch:
77
pull_request:
88
types: [opened, synchronize, reopened]
99
paths:

.github/workflows/test.yml

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# On-target hardware tests on self-hosted runners.
22
#
33
# Triggers:
4-
# - push to main: runs every enabled test in .github/test/tests.yml
5-
# - schedule (03:00 UTC nightly): same as push to main
6-
# - workflow_dispatch: pick branch (GitHub branch dropdown) and test (all or one id)
4+
# - workflow_call: invoked by ci.yml (prebuilt firmware) or manually from other workflows
5+
# - workflow_dispatch: pick branch and test (all or one id from .github/test/tests.yml)
76
#
87
# When adding tests, update workflow_dispatch options below and .github/test/tests.yml.
98
#
@@ -30,11 +29,19 @@
3029
name: Test
3130

3231
on:
33-
push:
34-
branches:
35-
- main
36-
schedule:
37-
- cron: '0 3 * * *'
32+
workflow_call:
33+
inputs:
34+
test:
35+
description: Test id to run, or all for every enabled test
36+
required: false
37+
type: string
38+
default: all
39+
use_prebuilt_firmware:
40+
description: Flash firmware downloaded from the Build workflow instead of rebuilding
41+
required: false
42+
type: boolean
43+
default: false
44+
secrets: inherit
3845
workflow_dispatch:
3946
inputs:
4047
test:
@@ -46,12 +53,17 @@ on:
4653
- all
4754
- 91m1_ppp-cloud-connect-nrf54l15
4855
- 91m1_ppp-application-fota-nrf54l15
56+
use_prebuilt_firmware:
57+
description: Download firmware artifacts from a prior Build run in this workflow
58+
required: false
59+
type: boolean
60+
default: false
4961

5062
permissions:
5163
contents: read
5264

5365
concurrency:
54-
group: hardware-test-${{ github.ref }}
66+
group: hardware-test-${{ github.ref }}-${{ github.run_id }}
5567
cancel-in-progress: false
5668

5769
jobs:
@@ -71,7 +83,7 @@ jobs:
7183
id: resolve
7284
working-directory: serial-modem-host-applications
7385
env:
74-
TEST_FILTER: ${{ github.event_name == 'workflow_dispatch' && inputs.test || 'all' }}
86+
TEST_FILTER: ${{ inputs.test || 'all' }}
7587
run: |
7688
pip install --quiet -r tests/on_target/requirements.txt
7789
PYTHONPATH=tests/on_target python3 -m ci.catalog matrix --filter "$TEST_FILTER"
@@ -92,6 +104,7 @@ jobs:
92104
env:
93105
CMAKE_PREFIX_PATH: /opt/toolchains
94106
NRFUTIL_HOME: /usr/local/share/nrfutil
107+
CI_USE_PREBUILT_FIRMWARE: ${{ inputs.use_prebuilt_firmware && '1' || '0' }}
95108
NRF_CLOUD_API_KEY: ${{ secrets.NRF_CLOUD_API_KEY }}
96109
NRF_CLOUD_CA_CERT: ${{ secrets.NRF_CLOUD_CA_CERT }}
97110
NRF_CLOUD_CA_KEY: ${{ secrets.NRF_CLOUD_CA_KEY }}
@@ -122,6 +135,13 @@ jobs:
122135
echo "EOF"
123136
} >> "$GITHUB_OUTPUT"
124137
138+
- name: Download prebuilt firmware
139+
if: inputs.use_prebuilt_firmware
140+
uses: actions/download-artifact@v4
141+
with:
142+
name: firmware-${{ matrix.app }}
143+
path: serial-modem-host-applications/applications/${{ matrix.app }}/build
144+
125145
- name: Initialize West workspace
126146
uses: ./serial-modem-host-applications/.github/actions/west-init
127147

@@ -172,7 +192,7 @@ jobs:
172192
-v
173193
174194
- name: Upload DuT firmware artifacts
175-
if: always()
195+
if: always() && inputs.use_prebuilt_firmware != true
176196
uses: actions/upload-artifact@v4
177197
with:
178198
name: dut-firmware-${{ matrix.app }}-${{ matrix.id }}-${{ github.run_id }}

.gitlint

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ debug = false
1313

1414
# Set the extra-path where gitlint will search for user defined rules
1515
# See http://jorisroovers.github.io/gitlint/user_defined_rules for details
16-
extra-path=../zephyr/scripts/gitlint
16+
extra-path=scripts/gitlint
1717

1818
[title-max-length-no-revert]
1919
line-length=75
@@ -24,8 +24,8 @@ min-line-count=1
2424
[body-max-line-count]
2525
max-line-count=200
2626

27-
[title-starts-with-subsystem]
28-
regex = ^(?!subsys:)(([^:]+):)(\s([^:]+):)*\s(.+)$
27+
[title-conventional-semver-zephyr]
28+
regex = ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(!)?: (?!subsys:)(([^:]+):)(\s([^:]+):)*\s(.+)$
2929

3030
[title-must-not-contain-word]
3131
# Comma-separated list of words that should not occur in the title. Matching is case

0 commit comments

Comments
 (0)