1212# existing entry (e.g. Pi64, which isn't published to rpi-imager today) is
1313# skipped with a warning rather than guessing at a new entry's "devices"
1414# list, icon, etc.
15+ #
16+ # The four platforms' downloads/hashing run as parallel matrix jobs (each
17+ # multi-GB image is fetched+extracted+hashed independently) rather than one
18+ # long sequential loop -- they don't depend on each other. A final job
19+ # gathers the per-platform results and does the JSON update + PR.
1520# ############################################################################
1621
1722name : Update RPi Imager JSON
@@ -38,16 +43,13 @@ permissions:
3843 pull-requests : write
3944
4045jobs :
41- update :
46+ resolve :
4247 runs-on : ubuntu-latest
4348 env :
4449 GH_TOKEN : ${{ github.token }}
50+ outputs :
51+ tag : ${{ steps.release.outputs.tag }}
4552 steps :
46- - name : Checkout master
47- uses : actions/checkout@v5
48- with :
49- ref : master
50-
5153 - name : Resolve release
5254 id : release
5355 run : |
@@ -81,108 +83,130 @@ jobs:
8183 # tag_name/published_at -- using those here previously overwrote
8284 # correct "9.5.3" entries back down to "9.5" (PR #2772).
8385
84- - name : Download platform images and compute hashes
86+ - name : Upload release.json
87+ uses : actions/upload-artifact@v4
88+ with :
89+ name : release-json
90+ path : release.json
91+ retention-days : 1
92+
93+ hashes :
94+ needs : resolve
95+ runs-on : ubuntu-latest
96+ strategy :
97+ fail-fast : false
98+ matrix :
99+ # Suffix must match the trailing "-<suffix>.img.zip" of the asset
100+ # filename exactly (a plain substring match would let "Pi" match
101+ # "Pi64" too).
102+ suffix : [Pi, Pi64, BB64, BBB]
103+ steps :
104+ - name : Download release.json
105+ uses : actions/download-artifact@v4
106+ with :
107+ name : release-json
108+
109+ - name : Download image and compute hashes
85110 id : hashes
111+ env :
112+ SUFFIX : ${{ matrix.suffix }}
86113 run : |
87114 set -euo pipefail
88115 mkdir -p work
89- # Suffix must match the trailing "-<suffix>.img.zip" of the asset
90- # filename exactly (a plain substring match would let "Pi" match
91- # "Pi64" too).
92- for SUFFIX in Pi Pi64 BB64 BBB; do
93- MATCH=$(jq -c --arg s "$SUFFIX" \
94- '[.assets[] | select(.name | test("-" + $s + "\\.img\\.zip$"))] | sort_by(.updated_at) | last // empty' \
95- release.json)
96-
97- if [ -z "$MATCH" ]; then
98- echo "No .img.zip asset for platform $SUFFIX in this release -- skipping."
99- continue
100- fi
101-
102- NAME=$(jq -r '.name' <<<"$MATCH")
103- URL=$(jq -r '.browser_download_url' <<<"$MATCH")
104- # Per-asset, not per-release: this repo re-uploads patch bumps
105- # (9.5, 9.5.1, ..., 9.5.3) onto the SAME release/tag over time,
106- # so the release object's own tag_name/published_at are stale
107- # for anything past the first upload (see the note above).
108- ASSET_DATE=$(jq -r '(.updated_at // .created_at)' <<<"$MATCH" | cut -d'T' -f1)
109- # Asset filenames are FPP-v<VERSION>-<SUFFIX>.img.zip (see
110- # SD/build-image-pi.sh's OUT_IMG); strip the fixed prefix/suffix
111- # to recover VERSION even though it can differ from the release
112- # tag_name (FPP-v9.5.3-Pi.img.zip on tag "9.5").
113- ASSET_VERSION="${NAME#FPP-v}"
114- ASSET_VERSION="${ASSET_VERSION%-${SUFFIX}.img.zip}"
115-
116- echo "=== $SUFFIX: $NAME (version $ASSET_VERSION, $ASSET_DATE) ==="
117- ZIP="work/$NAME"
118- curl -fL --retry 3 -o "$ZIP" "$URL"
119-
120- ZIP_SIZE=$(stat -c%s "$ZIP")
121- ZIP_SHA=$(sha256sum "$ZIP" | awk '{print $1}')
122-
123- IMG_DIR="work/extract-$SUFFIX"
124- rm -rf "$IMG_DIR"
125- mkdir -p "$IMG_DIR"
126- unzip -q "$ZIP" -d "$IMG_DIR"
127- IMG_FILE=$(find "$IMG_DIR" -maxdepth 1 -name '*.img' | head -n1)
128- if [ -z "$IMG_FILE" ]; then
129- echo "::error::No .img found inside $NAME" >&2
130- exit 1
131- fi
132-
133- IMG_SIZE=$(stat -c%s "$IMG_FILE")
134- IMG_SHA=$(sha256sum "$IMG_FILE" | awk '{print $1}')
135-
136- # Free the extracted .img right away -- these are multi-GB and
137- # four platforms in a row will exhaust the runner's disk otherwise.
138- rm -rf "$IMG_DIR"
139- rm -f "$ZIP"
140-
141- {
142- echo "${SUFFIX}_url=$URL"
143- echo "${SUFFIX}_version=$ASSET_VERSION"
144- echo "${SUFFIX}_release_date=$ASSET_DATE"
145- echo "${SUFFIX}_image_download_size=$ZIP_SIZE"
146- echo "${SUFFIX}_image_download_sha256=$ZIP_SHA"
147- echo "${SUFFIX}_extract_size=$IMG_SIZE"
148- echo "${SUFFIX}_extract_sha256=$IMG_SHA"
149- } >> "$GITHUB_OUTPUT"
150- done
116+
117+ MATCH=$(jq -c --arg s "$SUFFIX" \
118+ '[.assets[] | select(.name | test("-" + $s + "\\.img\\.zip$"))] | sort_by(.updated_at) | last // empty' \
119+ release.json)
120+
121+ if [ -z "$MATCH" ]; then
122+ echo "No .img.zip asset for platform $SUFFIX in this release -- skipping."
123+ exit 0
124+ fi
125+
126+ NAME=$(jq -r '.name' <<<"$MATCH")
127+ URL=$(jq -r '.browser_download_url' <<<"$MATCH")
128+ # Per-asset, not per-release: this repo re-uploads patch bumps
129+ # (9.5, 9.5.1, ..., 9.5.3) onto the SAME release/tag over time,
130+ # so the release object's own tag_name/published_at are stale
131+ # for anything past the first upload (see resolve job's note).
132+ ASSET_DATE=$(jq -r '(.updated_at // .created_at)' <<<"$MATCH" | cut -d'T' -f1)
133+ # Asset filenames are FPP-v<VERSION>-<SUFFIX>.img.zip (see
134+ # SD/build-image-pi.sh's OUT_IMG); strip the fixed prefix/suffix
135+ # to recover VERSION even though it can differ from the release
136+ # tag_name (FPP-v9.5.3-Pi.img.zip on tag "9.5").
137+ ASSET_VERSION="${NAME#FPP-v}"
138+ ASSET_VERSION="${ASSET_VERSION%-${SUFFIX}.img.zip}"
139+
140+ echo "=== $SUFFIX: $NAME (version $ASSET_VERSION, $ASSET_DATE) ==="
141+ ZIP="work/$NAME"
142+ curl -fL --retry 3 -o "$ZIP" "$URL"
143+
144+ ZIP_SIZE=$(stat -c%s "$ZIP")
145+ ZIP_SHA=$(sha256sum "$ZIP" | awk '{print $1}')
146+
147+ IMG_DIR="work/extract-$SUFFIX"
148+ rm -rf "$IMG_DIR"
149+ mkdir -p "$IMG_DIR"
150+ unzip -q "$ZIP" -d "$IMG_DIR"
151+ IMG_FILE=$(find "$IMG_DIR" -maxdepth 1 -name '*.img' | head -n1)
152+ if [ -z "$IMG_FILE" ]; then
153+ echo "::error::No .img found inside $NAME" >&2
154+ exit 1
155+ fi
156+
157+ IMG_SIZE=$(stat -c%s "$IMG_FILE")
158+ IMG_SHA=$(sha256sum "$IMG_FILE" | awk '{print $1}')
159+
160+ # Free the extracted .img right away -- these are multi-GB and
161+ # each matrix job only has one platform's worth of disk headroom.
162+ rm -rf "$IMG_DIR"
163+ rm -f "$ZIP"
164+
165+ jq -n \
166+ --arg url "$URL" \
167+ --arg version "$ASSET_VERSION" \
168+ --arg release_date "$ASSET_DATE" \
169+ --argjson image_download_size "$ZIP_SIZE" \
170+ --arg image_download_sha256 "$ZIP_SHA" \
171+ --argjson extract_size "$IMG_SIZE" \
172+ --arg extract_sha256 "$IMG_SHA" \
173+ '{url: $url, version: $version, release_date: $release_date,
174+ image_download_size: $image_download_size, image_download_sha256: $image_download_sha256,
175+ extract_size: $extract_size, extract_sha256: $extract_sha256}' \
176+ > "hashes-${SUFFIX}.json"
177+
178+ - name : Upload hashes
179+ uses : actions/upload-artifact@v4
180+ if : hashFiles(format('hashes-{0}.json', matrix.suffix)) != ''
181+ with :
182+ name : hashes-${{ matrix.suffix }}
183+ path : hashes-${{ matrix.suffix }}.json
184+ retention-days : 1
185+
186+ update :
187+ needs : [resolve, hashes]
188+ runs-on : ubuntu-latest
189+ env :
190+ GH_TOKEN : ${{ github.token }}
191+ TAG : ${{ needs.resolve.outputs.tag }}
192+ steps :
193+ - name : Checkout master
194+ uses : actions/checkout@v5
195+ with :
196+ ref : master
197+
198+ - name : Download per-platform hashes
199+ uses : actions/download-artifact@v4
200+ with :
201+ pattern : hashes-*
202+ path : hashes
203+ merge-multiple : false
151204
152205 - name : Update JSON
153206 id : update
154- env :
155- TAG : ${{ steps.release.outputs.tag }}
156- Pi_url : ${{ steps.hashes.outputs.Pi_url }}
157- Pi_version : ${{ steps.hashes.outputs.Pi_version }}
158- Pi_release_date : ${{ steps.hashes.outputs.Pi_release_date }}
159- Pi_image_download_size : ${{ steps.hashes.outputs.Pi_image_download_size }}
160- Pi_image_download_sha256 : ${{ steps.hashes.outputs.Pi_image_download_sha256 }}
161- Pi_extract_size : ${{ steps.hashes.outputs.Pi_extract_size }}
162- Pi_extract_sha256 : ${{ steps.hashes.outputs.Pi_extract_sha256 }}
163- Pi64_url : ${{ steps.hashes.outputs.Pi64_url }}
164- Pi64_version : ${{ steps.hashes.outputs.Pi64_version }}
165- Pi64_release_date : ${{ steps.hashes.outputs.Pi64_release_date }}
166- Pi64_image_download_size : ${{ steps.hashes.outputs.Pi64_image_download_size }}
167- Pi64_image_download_sha256 : ${{ steps.hashes.outputs.Pi64_image_download_sha256 }}
168- Pi64_extract_size : ${{ steps.hashes.outputs.Pi64_extract_size }}
169- Pi64_extract_sha256 : ${{ steps.hashes.outputs.Pi64_extract_sha256 }}
170- BB64_url : ${{ steps.hashes.outputs.BB64_url }}
171- BB64_version : ${{ steps.hashes.outputs.BB64_version }}
172- BB64_release_date : ${{ steps.hashes.outputs.BB64_release_date }}
173- BB64_image_download_size : ${{ steps.hashes.outputs.BB64_image_download_size }}
174- BB64_image_download_sha256 : ${{ steps.hashes.outputs.BB64_image_download_sha256 }}
175- BB64_extract_size : ${{ steps.hashes.outputs.BB64_extract_size }}
176- BB64_extract_sha256 : ${{ steps.hashes.outputs.BB64_extract_sha256 }}
177- BBB_url : ${{ steps.hashes.outputs.BBB_url }}
178- BBB_version : ${{ steps.hashes.outputs.BBB_version }}
179- BBB_release_date : ${{ steps.hashes.outputs.BBB_release_date }}
180- BBB_image_download_size : ${{ steps.hashes.outputs.BBB_image_download_size }}
181- BBB_image_download_sha256 : ${{ steps.hashes.outputs.BBB_image_download_sha256 }}
182- BBB_extract_size : ${{ steps.hashes.outputs.BBB_extract_size }}
183- BBB_extract_sha256 : ${{ steps.hashes.outputs.BBB_extract_sha256 }}
184207 run : |
185208 python3 <<'PY'
209+ import glob
186210 import json
187211 import os
188212 import re
@@ -243,19 +267,25 @@ jobs:
243267 m = name_re.match(o.get("name", ""))
244268 return channel_of(m.group(1)) if m else "stable"
245269
270+ # Each parallel "hashes" matrix job dropped hashes-<SUFFIX>.json
271+ # (absent if that platform had no asset in this release).
272+ per_platform = {}
273+ for f in glob.glob("hashes/hashes-*/hashes-*.json"):
274+ suffix = re.match(r"hashes-(.+)\.json$", os.path.basename(f)).group(1)
275+ with open(f) as fh:
276+ per_platform[suffix] = json.load(fh)
277+
246278 added, updated, removed_dupes, superseded, skipped = [], [], [], [], []
247279 channels_seen = set()
248280
249281 for suffix in PLATFORM_SUFFIXES:
250- url = os.environ. get(f"{ suffix}_url", "" )
251- if not url :
282+ fields = per_platform. get(suffix)
283+ if not fields :
252284 continue # no asset for this platform in the release
253285
254- # Per-platform, not per-release: see the note by the "Resolve
255- # release" step -- a single release/tag can carry several
256- # differently-versioned assets uploaded over time.
257- version = os.environ[f"{suffix}_version"]
258- release_date = os.environ[f"{suffix}_release_date"]
286+ url = fields["url"]
287+ version = fields["version"]
288+ release_date = fields["release_date"]
259289 new_channel = channel_of(version)
260290 new_family = family_of(version)
261291 channels_seen.add(new_channel)
@@ -324,10 +354,10 @@ jobs:
324354 label = m.group(2) if m else suffix
325355 entry["name"] = f"FPP v{version} {label}"
326356 entry["url"] = url
327- entry["image_download_size"] = int(os.environ[f"{suffix}_image_download_size "])
328- entry["image_download_sha256"] = os.environ[f"{suffix}_image_download_sha256 "]
329- entry["extract_size"] = int(os.environ[f"{suffix}_extract_size "])
330- entry["extract_sha256"] = os.environ[f"{suffix}_extract_sha256 "]
357+ entry["image_download_size"] = int(fields["image_download_size "])
358+ entry["image_download_sha256"] = fields["image_download_sha256 "]
359+ entry["extract_size"] = int(fields["extract_size "])
360+ entry["extract_sha256"] = fields["extract_sha256 "]
331361 entry["release_date"] = release_date
332362
333363 with open(path, "w") as f:
@@ -356,7 +386,6 @@ jobs:
356386 - name : Open PR
357387 if : ${{ inputs.dry_run != true && steps.update.outputs.changed != '' }}
358388 env :
359- TAG : ${{ steps.release.outputs.tag }}
360389 CHANNEL : ${{ steps.update.outputs.channel }}
361390 ADDED : ${{ steps.update.outputs.added }}
362391 UPDATED : ${{ steps.update.outputs.updated }}
0 commit comments