-
Notifications
You must be signed in to change notification settings - Fork 1
298 lines (266 loc) · 12.2 KB
/
flasher.yml
File metadata and controls
298 lines (266 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
name: Web Flasher
on:
push:
branches: [main, develop]
paths:
- "firmware/**"
- "docs/flash/**"
- "docs/manual/**"
- ".github/workflows/flasher.yml"
pull_request:
branches: [main, develop]
paths:
- "firmware/**"
- "docs/flash/**"
- "docs/manual/**"
- ".github/workflows/flasher.yml"
workflow_dispatch:
permissions:
contents: read
# Pages deploys serialize globally (one Pages site per repo, latest run wins).
# PR builds use a per-PR group so they don't fight the deploy lane.
concurrency:
group: ${{ github.event_name == 'pull_request' && format('pages-pr-{0}', github.head_ref) || 'pages' }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# Both checkouts need fetch-depth: 0 (full history + tags) so the
# firmware build's `git describe --tags --dirty --always` resolves
# to a real version on tagged release commits instead of just the
# short SHA.
# main side: PR head if PR targets main, otherwise the main branch tip.
- name: Checkout main side
uses: actions/checkout@v6
with:
ref: ${{ (github.event_name == 'pull_request' && github.base_ref == 'main') && github.head_ref || 'main' }}
path: src/main
fetch-depth: 0
# develop side: PR head if PR targets develop, otherwise the develop branch tip.
- name: Checkout develop side
uses: actions/checkout@v6
with:
ref: ${{ (github.event_name == 'pull_request' && github.base_ref == 'develop') && github.head_ref || 'develop' }}
path: src/develop
fetch-depth: 0 # see comment above the main-side checkout
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Cache PlatformIO
uses: actions/cache@v5
with:
path: |
~/.platformio
~/.cache/pip
key: pio-${{ runner.os }}-${{ hashFiles('src/main/firmware/platformio.ini', 'src/develop/firmware/platformio.ini') }}
restore-keys: |
pio-${{ runner.os }}-
- name: Install PlatformIO
run: pip install --upgrade platformio
# Per-side firmware build cache. The key shape is:
# fw-<os>-<side>-v1-<hashFiles>-<tag>
# where <side>=main|develop keeps the two branches' caches isolated,
# `v1` is a manual bump knob to force a global rebuild, and the two
# dynamic components are:
# 1. hashFiles(src/<side>/firmware/**, src/<side>/.github/workflows/flasher.yml)
# — firmware source OR build glue changes invalidate the cache
# without operator intervention. The workflow path is per-side
# because each branch is checked out under src/<side>/, never
# at the workspace root, so a top-level glob would match zero
# files and silently contribute nothing to the hash.
# 2. Latest reachable tag (`git describe --tags --abbrev=0`) — when
# scripts/release.sh re-runs main CI right after pushing the new
# tag (release flow step 9), the tag becomes visible, the key
# changes, the cache misses, and `pio run` re-bakes the freshly
# pushed `vX.Y.Z` into FIRMWARE_VERSION via inject_version.py.
# Without this, the post-tag rerun would silently hit cache and
# ship a binary stamped against the previous tag (cosmetic on
# /flash/ — iOS only compares the leading major component — but
# worth the cache-bust).
# Docs-only pushes share both dynamic components and hit cache.
#
# Restore + Save are split (instead of `actions/cache@v5`) so the save
# step can be gated on build success — a `pio run` killed mid-link
# by `cancel-in-progress: true` (line 27) leaves a partial `.pio/build`
# that would otherwise be saved and silently reused on the next run
# with the same key.
#
# boot_app0.bin lives inside the PlatformIO Arduino-ESP32 framework
# package and is fetched only when `pio run` actually executes. The
# build step copies it into .pio/build/m5stick-s3/ so the firmware
# cache carries it; without that, a firmware-cache hit + a
# PlatformIO-cache miss (independent eviction lifecycles) would leave
# the staging step unable to find boot_app0.bin.
- name: Resolve firmware tag (main)
id: fw-tag-main
run: echo "tag=$(git -C src/main describe --tags --abbrev=0 2>/dev/null || echo none)" >> "$GITHUB_OUTPUT"
- name: Restore firmware build cache (main)
id: cache-fw-main
uses: actions/cache/restore@v5
with:
path: src/main/firmware/.pio/build
key: fw-${{ runner.os }}-main-v1-${{ hashFiles('src/main/firmware/**', 'src/main/.github/workflows/flasher.yml') }}-${{ steps.fw-tag-main.outputs.tag }}
- name: Build firmware (main)
id: build-fw-main
if: steps.cache-fw-main.outputs.cache-hit != 'true'
working-directory: src/main/firmware
run: |
set -euo pipefail
pio run -e m5stick-s3
BOOT_APP0=$(find ~/.platformio/packages/framework-arduinoespressif32 -name boot_app0.bin -print -quit)
if [ -z "$BOOT_APP0" ]; then
echo "boot_app0.bin not found in PlatformIO Arduino-ESP32 framework" >&2
exit 1
fi
cp "$BOOT_APP0" .pio/build/m5stick-s3/boot_app0.bin
- name: Save firmware build cache (main)
if: steps.cache-fw-main.outputs.cache-hit != 'true' && steps.build-fw-main.outcome == 'success'
uses: actions/cache/save@v5
with:
path: src/main/firmware/.pio/build
key: ${{ steps.cache-fw-main.outputs.cache-primary-key }}
- name: Resolve firmware tag (develop)
id: fw-tag-develop
run: echo "tag=$(git -C src/develop describe --tags --abbrev=0 2>/dev/null || echo none)" >> "$GITHUB_OUTPUT"
- name: Restore firmware build cache (develop)
id: cache-fw-develop
uses: actions/cache/restore@v5
with:
path: src/develop/firmware/.pio/build
key: fw-${{ runner.os }}-develop-v1-${{ hashFiles('src/develop/firmware/**', 'src/develop/.github/workflows/flasher.yml') }}-${{ steps.fw-tag-develop.outputs.tag }}
- name: Build firmware (develop)
id: build-fw-develop
if: steps.cache-fw-develop.outputs.cache-hit != 'true'
working-directory: src/develop/firmware
run: |
set -euo pipefail
pio run -e m5stick-s3
BOOT_APP0=$(find ~/.platformio/packages/framework-arduinoespressif32 -name boot_app0.bin -print -quit)
if [ -z "$BOOT_APP0" ]; then
echo "boot_app0.bin not found in PlatformIO Arduino-ESP32 framework" >&2
exit 1
fi
cp "$BOOT_APP0" .pio/build/m5stick-s3/boot_app0.bin
- name: Save firmware build cache (develop)
if: steps.cache-fw-develop.outputs.cache-hit != 'true' && steps.build-fw-develop.outcome == 'success'
uses: actions/cache/save@v5
with:
path: src/develop/firmware/.pio/build
key: ${{ steps.cache-fw-develop.outputs.cache-primary-key }}
- name: Stage + verify firmware bins
run: |
set -euo pipefail
# boot_app0.bin is copied into each side's .pio/build/m5stick-s3/
# by the build step, so it rides along in the firmware cache and
# is available even when the PlatformIO framework cache has been
# evicted.
stage_side() {
local side="$1"
local out="src/${side}/docs/flash/firmware"
local build="src/${side}/firmware/.pio/build/m5stick-s3"
mkdir -p "$out"
cp "$build/bootloader.bin" "$out/bootloader.bin"
cp "$build/partitions.bin" "$out/partitions.bin"
cp "$build/firmware.bin" "$out/hdzap.bin"
cp "$build/boot_app0.bin" "$out/boot_app0.bin"
(cd "$out" && \
for f in bootloader.bin partitions.bin boot_app0.bin hdzap.bin; do
if [ ! -f "$f" ]; then
echo "[$side] missing artefact: $f" >&2
exit 1
fi
sz=$(stat -c%s "$f")
if [ "$sz" -lt 1024 ]; then
echo "[$side] $f is suspiciously small: ${sz} bytes (expected >= 1 KiB)" >&2
exit 1
fi
done
sha256sum *.bin > CHECKSUMS.txt
echo "=== ${side} CHECKSUMS ==="
cat CHECKSUMS.txt)
}
stage_side main
stage_side develop
- name: Stamp manifest versions
run: |
set -euo pipefail
python - <<'PY'
import json, pathlib, subprocess
for branch, side in [("main", "src/main"), ("develop", "src/develop")]:
sha = subprocess.check_output(
["git", "-C", side, "rev-parse", "--short", "HEAD"]
).decode().strip()
p = pathlib.Path(side) / "docs/flash/manifest.json"
m = json.loads(p.read_text())
m["version"] = f"{branch}-{sha}"
p.write_text(json.dumps(m, indent=2) + "\n")
print(f"{branch}: {m['version']}")
PY
- name: Compose Pages artefact (main at root, develop under /dev/)
run: |
set -euo pipefail
rm -rf _site
stage_pages() {
local side="$1" # src/main or src/develop
local dest="$2" # _site or _site/dev
mkdir -p "$dest/flash" "$dest/ja" "$dest/images" "$dest/ja/images" \
"$dest/privacy"
# Web Flasher
cp -r "$side/docs/flash/." "$dest/flash/"
# Manual: en at $dest, ja at $dest/ja, images mirrored under both. Each per-side
# checkout may legitimately omit a page (a page added on develop hasn't reached
# main until release), so guard each cp so one missing file doesn't break the
# composite — the other side still publishes its own slice.
cp "$side/docs/manual/_pages/index.html" "$dest/index.html"
cp "$side/docs/manual/_pages/ja/index.html" "$dest/ja/index.html"
if [ -f "$side/docs/manual/_pages/privacy/index.html" ]; then
cp "$side/docs/manual/_pages/privacy/index.html" "$dest/privacy/index.html"
fi
if [ -f "$side/docs/manual/_pages/privacy/ja/index.html" ]; then
mkdir -p "$dest/privacy/ja"
cp "$side/docs/manual/_pages/privacy/ja/index.html" "$dest/privacy/ja/index.html"
fi
cp "$side/docs/manual/en.md" "$dest/index.md"
cp "$side/docs/manual/ja.md" "$dest/ja/index.md"
cp -r "$side/docs/manual/images/." "$dest/images/"
cp -r "$side/docs/manual/images/." "$dest/ja/images/"
}
stage_pages src/main _site
stage_pages src/develop _site/dev
# Favicons live at root only (browser auto-discovery scope is the host).
cp src/main/docs/manual/favicon.ico _site/favicon.ico
cp src/main/docs/manual/favicon-16x16.png _site/favicon-16x16.png
cp src/main/docs/manual/favicon-32x32.png _site/favicon-32x32.png
cp src/main/docs/manual/apple-touch-icon.png _site/apple-touch-icon.png
# Disable Jekyll — manual pages render markdown client-side via marked.js.
touch _site/.nojekyll
echo "=== root ==="
ls -la _site
echo "=== /flash/ ==="
ls -la _site/flash
echo "=== /dev/ ==="
ls -la _site/dev
echo "=== /dev/flash/ ==="
ls -la _site/dev/flash
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: _site
deploy:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5