-
Notifications
You must be signed in to change notification settings - Fork 0
232 lines (217 loc) · 9.69 KB
/
Copy pathplatformio.yml
File metadata and controls
232 lines (217 loc) · 9.69 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
# PlatformIO Docker Build Workflow
#
# Build Strategy:
# - Main repo master: Build + Push to GHCR
# - Main repo dev/PR: Build only (validation) - nothing is uploaded. Unlike
# esp-idf.yml, no other job consumes this image, so there is nothing to push for.
# - workflow_dispatch: Build on any branch of the main repo, push only on master
# - Forks: nothing runs - the runner pools are not reachable there; use
# ./scripts/build.sh instead
# - Multi-platform: Platform matrix builds linux/amd64 and linux/arm64 in parallel,
# each on a runner of its own architecture (matrix.runner) - no QEMU
name: 🐳 PlatformIO Docker Image
on:
push:
branches: [master, dev]
# Negations come last and are order-sensitive; `paths:` and `paths-ignore:`
# cannot both be used for one event. Documentation-only commits used to
# trigger a full rebuild and republish every tag - the docs-only merge that
# started this series set off a 5.5-hour ESP-Matter leg.
paths:
- '.github/workflows/platformio.yml'
- 'images/platformio/**'
- 'images/versions.json'
- 'scripts/versions-matrix.sh'
- 'scripts/check-versions.sh'
- '!images/**/*.md'
pull_request:
branches: [master, dev]
paths:
- '.github/workflows/platformio.yml'
- 'images/platformio/**'
- 'images/versions.json'
- 'scripts/versions-matrix.sh'
- 'scripts/check-versions.sh'
- '!images/**/*.md'
workflow_dispatch:
env:
REGISTRY: ghcr.io
PLATFORMIO_IMAGE_NAME: jethome-dev-platformio
# build-push-action v6+ uploads a build record as a workflow artifact by default.
# This repository is public, so that archive - full build log and metadata - would
# be downloadable by anyone off the run page, on every leg of every push. The
# summary itself is kept: it reports what was built without opening the log, and
# costs no storage.
DOCKER_BUILD_RECORD_UPLOAD: false
# Superseded pull-request runs are cancelled - nothing has been published yet and
# they cost money. Pushes are deliberately NOT grouped together: the group key
# includes the commit, so each one gets its own group and none can cancel another.
#
# `cancel-in-progress: false` would not have given a queue. GitHub cancels a
# *pending* run in a group whenever a newer one arrives, regardless of that flag -
# so grouping master pushes by ref would silently drop the middle commit of any
# three that land inside one build window, and its sha-<short-commit> image, which the
# READMEs document as the way to pin an exact commit, would never exist.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'pull_request' && 'pr' || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Both matrices come from images/versions.json, so a version lives in one place
# instead of two matrix blocks that have to be edited together. The consistency
# check runs first and fails the whole workflow.
prepare:
name: 🧮 Resolve matrices
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.repository_owner == 'jethome-iot' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
permissions:
contents: read
outputs:
build: ${{ steps.matrices.outputs.build }}
manifest: ${{ steps.matrices.outputs.manifest }}
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v7
- name: ✅ Check version consistency
run: ./scripts/check-versions.sh
- name: 🧮 Generate matrices
id: matrices
run: |
set -euo pipefail
# Assigned first, echoed second: `echo "k=$(cmd)"` takes echo's exit
# status, so a failing generator would write an empty matrix and leave
# this step green.
build=$(./scripts/versions-matrix.sh platformio build)
manifest=$(./scripts/versions-matrix.sh platformio manifest)
{
echo "build=${build}"
echo "manifest=${manifest}"
} >> "$GITHUB_OUTPUT"
platformio-build:
name: platformio-build (${{ matrix.tag }}, ${{ matrix.platform }})
needs: prepare
runs-on: ${{ matrix.runner }}
timeout-minutes: ${{ matrix.timeout_minutes }}
# Owner-only, with no workflow_dispatch escape hatch: the runner pools belong
# to jethome-iot, a fork cannot resolve their labels, and an unresolvable
# `runs-on` queues for 24 hours rather than failing. A fork that wants to build
# this image runs ./scripts/build.sh.
# Two conditions, not one. The owner check keeps this out of forks that run the
# workflow themselves. The head-repo check keeps it out of pull requests *from*
# a fork, where github.repository_owner is still jethome-iot - so without it a
# fork-controlled Dockerfile would execute on this org's paid runner pools.
if: github.repository_owner == 'jethome-iot' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.prepare.outputs.build) }}
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v7
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: 🔐 Log in to GitHub Container Registry
if: github.ref_name == 'master'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Pushed by digest, with no tag of its own: a tag here would be a mutable
# name that the manifest job below has to look up again later, and between
# those two moments another run can overwrite it. The digest is the only
# thing handed forward.
- name: 🐳 Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: images/platformio
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.ref_name == 'master' }}
build-args: ${{ matrix.build_args }}
# Matrix legs cannot each set a job output - they would overwrite one
# another - so the digests travel as one empty file per leg, named after the
# digest itself.
- name: 📤 Export digest
if: github.ref_name == 'master'
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: ⬆️ Upload digest
if: github.ref_name == 'master'
uses: actions/upload-artifact@v7
with:
name: digest-${{ env.PLATFORMIO_IMAGE_NAME }}-${{ matrix.tag }}-${{ matrix.platform_tag }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
platformio-manifest:
name: platformio-manifest (${{ matrix.tag }})
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [prepare, platformio-build]
# See the note in esp-idf.yml: one variant's failure must not withhold another
# variant's tags.
if: >-
${{ !cancelled()
&& needs.prepare.result == 'success'
&& github.repository_owner == 'jethome-iot'
&& github.ref_name == 'master' }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.prepare.outputs.manifest) }}
steps:
- name: 🔐 Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: ⬇️ Download digests
uses: actions/download-artifact@v8
with:
pattern: digest-${{ env.PLATFORMIO_IMAGE_NAME }}-${{ matrix.tag }}-*
merge-multiple: true
path: /tmp/digests
- name: 🐳 Create and push multi-arch manifest
env:
IMAGE: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.PLATFORMIO_IMAGE_NAME }}
TAG: ${{ matrix.tag }}
PRIMARY: ${{ matrix.primary }}
EXPECTED_PLATFORMS: ${{ matrix.platform_count }}
run: |
set -euo pipefail
cd /tmp/digests
found=$(find . -type f | wc -l | tr -d ' ')
if [ "${found}" -ne "${EXPECTED_PLATFORMS}" ]; then
echo "::error::expected ${EXPECTED_PLATFORMS} platform digests, found ${found}"
exit 1
fi
# Every source is a digest this run produced, so nothing here can be
# resolved to another run's image. Built as an array rather than an
# unquoted expansion so the refs survive as separate arguments without
# relying on word splitting.
refs=()
for digest in *; do
refs+=("${IMAGE}@sha256:${digest}")
done
# Only the primary variant moves `latest` and the bare `sha-<short-commit>`.
# Every variant gets `<version>-sha-<short-commit>`: the version tag itself is
# rewritten on every push, so without it a consumer pinned to a
# non-primary variant has no name for the build they were running.
SHA_SHORT="${GITHUB_SHA:0:7}"
tags=()
if [ "${PRIMARY}" = "true" ]; then
tags+=(-t "${IMAGE}:latest" -t "${IMAGE}:sha-${SHA_SHORT}")
fi
tags+=(-t "${IMAGE}:${TAG}-sha-${SHA_SHORT}")
tags+=(-t "${IMAGE}:${TAG}")
docker buildx imagetools create "${tags[@]}" "${refs[@]}"