Skip to content

Commit 3a38ef1

Browse files
committed
ci: Add CI workflows for building and releasing firmware
This commit introduces two new GitHub Actions workflows: `fobe_build.yml` for building firmware for specified boards and `fobe_release.yml` for managing releases. The build workflow handles cleaning the workspace, building the board, packaging binaries, and uploading them to releases or storing them as artifacts. The release workflow manages versioning, checks for existing releases, and creates or updates releases based on the build results. Signed-off-by: Chiho Sin <[email protected]>
1 parent dd9beda commit 3a38ef1

File tree

2 files changed

+324
-0
lines changed

2 files changed

+324
-0
lines changed

.github/workflows/fobe_build.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Build CI
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
repo_remote:
7+
required: false
8+
type: string
9+
default: origin
10+
repo_ref:
11+
required: false
12+
type: string
13+
default: ${{ github.sha }}
14+
port:
15+
required: true
16+
type: string
17+
board:
18+
required: true
19+
type: string
20+
firmware_tag:
21+
required: true
22+
type: string
23+
release_tag:
24+
required: false
25+
type: string
26+
27+
permissions:
28+
contents: write
29+
30+
jobs:
31+
build:
32+
name: Build ${{ inputs.board }}
33+
runs-on: self-hosted
34+
steps:
35+
- name: Clean workspace
36+
run: |
37+
sudo rm -rf bin
38+
sudo rm -rf ${{ inputs.board }}
39+
mkdir -p bin
40+
41+
- name: Build board ${{ inputs.board }}
42+
uses: fobe-projects/action-circuitpython-builder@main
43+
with:
44+
repo_remote: ${{ inputs.repo_remote }}
45+
repo_ref: ${{ inputs.repo_ref }}
46+
port: ${{ inputs.port }}
47+
board: ${{ inputs.board }}
48+
49+
- name: Package binaries
50+
run: |
51+
sudo mv bin ${{ inputs.board }}
52+
tar -cJf ${{ inputs.board }}${{ inputs.firmware_tag }}.tar.xz ${{ inputs.board }}
53+
54+
- name: Upload binaries to release
55+
if: ${{ inputs.release_tag != '' }}
56+
uses: softprops/action-gh-release@v2
57+
with:
58+
tag_name: ${{ inputs.release_tag }}
59+
files: ${{ inputs.board }}${{ inputs.firmware_tag }}.tar.xz
60+
token: ${{ secrets.GITHUB_TOKEN }}
61+
62+
- name: Store binaries as an artifact
63+
if: ${{ inputs.release_tag == '' }}
64+
uses: actions/upload-artifact@v4
65+
with:
66+
name: ${{ inputs.board }}${{ inputs.firmware_tag }}
67+
overwrite: true
68+
path: ${{ inputs.board }}${{ inputs.firmware_tag }}.tar.xz

.github/workflows/fobe_release.yml

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
name: Release CI
2+
3+
permissions:
4+
contents: write
5+
6+
concurrency:
7+
group: ci-${{ github.head_ref || github.run_id }}
8+
cancel-in-progress: true
9+
10+
on:
11+
workflow_dispatch:
12+
push:
13+
tags:
14+
- '*'
15+
16+
jobs:
17+
setup:
18+
name: Setup
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
port:
23+
- espressif
24+
- nordic
25+
runs-on: ubuntu-24.04
26+
steps:
27+
- uses: actions/checkout@v5
28+
- name: Generate matrix
29+
id: jsonStep
30+
run: |
31+
# Get all board directories for the specified port
32+
BOARDS_DIR="ports/${{matrix.port}}/boards"
33+
if [ -d "$BOARDS_DIR" ]; then
34+
# Get all directory names, exclude files, filter for fobe prefix, sort alphabetically
35+
BOARDS=$(find "$BOARDS_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | grep "^fobe" | sort | jq -R -s -c 'split("\n")[:-1]')
36+
else
37+
echo "Warning: Directory $BOARDS_DIR does not exist"
38+
BOARDS="[]"
39+
fi
40+
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
41+
echo "Port: ${{matrix.port}} Boards: $BOARDS"
42+
# Output the boards as a JSON object for the current port
43+
echo "${{matrix.port}}=$(jq -cn --argjson environments "$BOARDS" '{board: $environments}')" >> $GITHUB_OUTPUT
44+
outputs:
45+
espressif: ${{ steps.jsonStep.outputs.espressif }}
46+
nordic: ${{ steps.jsonStep.outputs.nordic }}
47+
48+
version:
49+
name: Determine Version
50+
runs-on: ubuntu-24.04
51+
steps:
52+
- uses: actions/checkout@v5
53+
with:
54+
fetch-depth: 0
55+
- name: Fetch all tags
56+
run: |
57+
# Add upstream remote and fetch tags from original CircuitPython repository
58+
git remote add upstream https://github.com/adafruit/circuitpython.git
59+
git fetch upstream --tags --prune --force
60+
git fetch origin --tags --prune --force
61+
- name: Get version using version.py
62+
run: |
63+
# Use version.py to get the current version
64+
RAW_VERSION=$(python3 py/version.py)
65+
echo "Raw version from py/version.py: $RAW_VERSION"
66+
67+
# Determine if this is a tagged version or a non-tagged version
68+
if [[ "$RAW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[0-9]+)?)?$ ]]; then
69+
# This is a tagged version (e.g., 10.0.0 or 10.0.0-beta.2 or 10.0.0-alpha.1)
70+
VERSION_TYPE="tagged"
71+
TARGET_VERSION="$RAW_VERSION"
72+
echo "This is a tagged version: $RAW_VERSION"
73+
elif [[ "$RAW_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[0-9]+)?)?)-[0-9]+-g[a-f0-9]+$ ]]; then
74+
# This is a non-tagged version (e.g., 10.0.0-beta.2-24-g2dc726497f)
75+
VERSION_TYPE="non_tagged"
76+
# Extract the tag part (everything before -N-gHASH)
77+
TARGET_VERSION=$(echo "$RAW_VERSION" | sed -E 's/-[0-9]+-g[a-f0-9]+$//')
78+
echo "This is a non-tagged version: $RAW_VERSION"
79+
echo "Target release tag: $TARGET_VERSION"
80+
else
81+
echo "❌ Error: Unrecognized version format: $RAW_VERSION"
82+
exit 1
83+
fi
84+
85+
# Generate firmware tag for file naming
86+
FW_DATE=$(date '+%Y%m%d')
87+
FW_TAG="-${FW_DATE}-${RAW_VERSION}"
88+
89+
echo "Version type: $VERSION_TYPE"
90+
echo "Raw version: $RAW_VERSION"
91+
echo "Target version: $TARGET_VERSION"
92+
echo "Firmware tag: $FW_TAG"
93+
94+
echo "version_type=${VERSION_TYPE}" >> $GITHUB_OUTPUT
95+
echo "raw_version=${RAW_VERSION}" >> $GITHUB_OUTPUT
96+
echo "target_version=${TARGET_VERSION}" >> $GITHUB_OUTPUT
97+
echo "firmware_tag=${FW_TAG}" >> $GITHUB_OUTPUT
98+
id: version
99+
outputs:
100+
version_type: ${{ steps.version.outputs.version_type }}
101+
raw_version: ${{ steps.version.outputs.raw_version }}
102+
target_version: ${{ steps.version.outputs.target_version }}
103+
firmware_tag: ${{ steps.version.outputs.firmware_tag }}
104+
105+
create-release:
106+
name: Create or Update Release
107+
needs: [setup, version]
108+
runs-on: ubuntu-24.04
109+
permissions:
110+
contents: write
111+
steps:
112+
- uses: actions/checkout@v5
113+
with:
114+
fetch-depth: 0
115+
- name: Determine release strategy
116+
id: strategy
117+
run: |
118+
VERSION_TYPE="${{ needs.version.outputs.version_type }}"
119+
RAW_VERSION="${{ needs.version.outputs.raw_version }}"
120+
TARGET_VERSION="${{ needs.version.outputs.target_version }}"
121+
122+
echo "Version type: $VERSION_TYPE"
123+
echo "Raw version: $RAW_VERSION"
124+
echo "Target version: $TARGET_VERSION"
125+
126+
# Set release information
127+
echo "tag_name=$TARGET_VERSION" >> $GITHUB_OUTPUT
128+
echo "release_name=CircuitPython Firmware $TARGET_VERSION" >> $GITHUB_OUTPUT
129+
echo "raw_version=$RAW_VERSION" >> $GITHUB_OUTPUT
130+
echo "target_version=$TARGET_VERSION" >> $GITHUB_OUTPUT
131+
echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT
132+
133+
- name: Check existing release and handle accordingly
134+
id: check_release
135+
run: |
136+
TAG_NAME="${{ steps.strategy.outputs.tag_name }}"
137+
VERSION_TYPE="${{ steps.strategy.outputs.version_type }}"
138+
RAW_VERSION="${{ steps.strategy.outputs.raw_version }}"
139+
140+
# Check if release exists
141+
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
142+
echo "Release $TAG_NAME exists"
143+
RELEASE_EXISTS=true
144+
else
145+
echo "Release $TAG_NAME does not exist"
146+
RELEASE_EXISTS=false
147+
fi
148+
149+
case "$VERSION_TYPE" in
150+
"tagged")
151+
if [ "$RELEASE_EXISTS" = true ]; then
152+
echo "❌ Release $TAG_NAME already exists for tagged version"
153+
echo "Skipping build to prevent duplicate releases"
154+
echo "continue=false" >> $GITHUB_OUTPUT
155+
exit 0
156+
else
157+
echo "✅ Tagged version $TAG_NAME does not have a release yet, proceeding with build"
158+
echo "continue=true" >> $GITHUB_OUTPUT
159+
echo "create_release=true" >> $GITHUB_OUTPUT
160+
echo "update_existing=false" >> $GITHUB_OUTPUT
161+
fi
162+
;;
163+
"non_tagged")
164+
if [ "$RELEASE_EXISTS" = true ]; then
165+
echo "✅ Found existing release $TAG_NAME for non-tagged version, will update it"
166+
echo "continue=true" >> $GITHUB_OUTPUT
167+
echo "create_release=false" >> $GITHUB_OUTPUT
168+
echo "update_existing=true" >> $GITHUB_OUTPUT
169+
else
170+
echo "❌ Target release $TAG_NAME does not exist for non-tagged version $RAW_VERSION"
171+
echo "Please create the target tag/release first: $TAG_NAME"
172+
echo "continue=false" >> $GITHUB_OUTPUT
173+
exit 0
174+
fi
175+
;;
176+
*)
177+
echo "❌ Error: Unknown version type: $VERSION_TYPE"
178+
echo "continue=false" >> $GITHUB_OUTPUT
179+
exit 1
180+
;;
181+
esac
182+
env:
183+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
184+
185+
- name: Update existing release
186+
if: steps.check_release.outputs.update_existing == 'true'
187+
run: |
188+
TAG_NAME="${{ steps.strategy.outputs.tag_name }}"
189+
RAW_VERSION="${{ steps.strategy.outputs.raw_version }}"
190+
191+
echo "Updating existing release $TAG_NAME with new build from $RAW_VERSION"
192+
193+
# Update the release body to reflect the latest build
194+
gh release edit "$TAG_NAME" \
195+
--title "${{ steps.strategy.outputs.release_name }}" \
196+
--notes "**Latest Build**: $RAW_VERSION
197+
**Commit**: ${{ github.sha }}
198+
199+
Please select the appropriate firmware file for your development board.
200+
201+
> ⚠️ Note: This release gets updated automatically with new builds from the development branch."
202+
env:
203+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
204+
205+
- name: Create Release
206+
if: steps.check_release.outputs.create_release == 'true'
207+
uses: softprops/action-gh-release@v2
208+
with:
209+
tag_name: ${{ steps.strategy.outputs.tag_name }}
210+
name: ${{ steps.strategy.outputs.release_name }}
211+
body: |
212+
**Version**: ${{ steps.strategy.outputs.raw_version }}
213+
**Commit**: ${{ github.sha }}
214+
215+
Please select the appropriate firmware file for your development board.
216+
draft: false
217+
prerelease: true
218+
token: ${{ secrets.GITHUB_TOKEN }}
219+
outputs:
220+
release_tag: ${{ steps.strategy.outputs.tag_name }}
221+
firmware_tag: ${{ needs.version.outputs.firmware_tag }}
222+
continue: ${{ steps.check_release.outputs.continue }}
223+
224+
build-espressif:
225+
name: Build Port Espressif
226+
needs: [setup, create-release]
227+
if: needs.create-release.outputs.continue == 'true'
228+
strategy:
229+
fail-fast: false
230+
matrix: ${{ fromJSON(needs.setup.outputs.espressif) }}
231+
uses: ./.github/workflows/fobe_build.yml
232+
with:
233+
repo_remote: origin
234+
repo_ref: ${{ github.sha }}
235+
port: espressif
236+
board: ${{ matrix.board }}
237+
firmware_tag: ${{ needs.create-release.outputs.firmware_tag }}
238+
release_tag: ${{ needs.create-release.outputs.release_tag }}
239+
secrets: inherit
240+
241+
build-nordic:
242+
name: Build Port Nordic
243+
needs: [setup, create-release]
244+
if: needs.create-release.outputs.continue == 'true'
245+
strategy:
246+
fail-fast: false
247+
matrix: ${{ fromJSON(needs.setup.outputs.nordic) }}
248+
uses: ./.github/workflows/fobe_build.yml
249+
with:
250+
repo_remote: origin
251+
repo_ref: ${{ github.sha }}
252+
port: nordic
253+
board: ${{ matrix.board }}
254+
firmware_tag: ${{ needs.create-release.outputs.firmware_tag }}
255+
release_tag: ${{ needs.create-release.outputs.release_tag }}
256+
secrets: inherit

0 commit comments

Comments
 (0)