Compare commits

..

No commits in common. "aa9b6913d6000bdd97260ab4411a8c4d7b763930" and "e8c9aedd2d1650e60bcdb3ec258548586d7dfee6" have entirely different histories.

6 changed files with 53 additions and 358 deletions

View file

@ -9,7 +9,7 @@ on:
type: string type: string
dry_run: dry_run:
description: 'Dry run: build and validate but do not create release' description: 'Dry run - build and validate but do not create release'
required: false required: false
type: boolean type: boolean
default: true default: true
@ -25,21 +25,6 @@ permissions:
env: env:
SOURCE_ID: official SOURCE_ID: official
# ---------------------------------------------------------------------------
# The Alchemist is not vendored here. It is checked out from the components
# repo at a ref pinned in alchemist/alchemist_ref, so the store repo carries
# only its own version_policy.sh (and optional version_pins.sh), which are
# overlaid onto the checkout.
#
# The ref is pinned rather than floating on main: an unpinned tool could
# change build output with no commit to this repo, and reuse would not notice
# because the fingerprint covers item folders only. A release could then mix
# artifacts built by two different tool versions.
# ---------------------------------------------------------------------------
ALCHEMIST_REPO: RetroDECK/components
ALCHEMIST_PATH: .alchemist
ALCHEMIST_DIR: .alchemist/automation-tools/alchemist
jobs: jobs:
# =========================================================================== # ===========================================================================
# Check whether this event should trigger a build # Check whether this event should trigger a build
@ -77,7 +62,7 @@ jobs:
# PR merge commits always trigger a build # PR merge commits always trigger a build
if echo "$COMMITS" | grep -qiE "^Merge pull request #"; then if echo "$COMMITS" | grep -qiE "^Merge pull request #"; then
echo "PR merge detected: auto-building" echo "PR merge detected - auto-building"
echo "should_build=true" >> $GITHUB_OUTPUT echo "should_build=true" >> $GITHUB_OUTPUT
echo "is_dry_run=false" >> $GITHUB_OUTPUT echo "is_dry_run=false" >> $GITHUB_OUTPUT
exit 0 exit 0
@ -97,7 +82,7 @@ jobs:
exit 0 exit 0
fi fi
echo "No build trigger found in commit messages: skipping" echo "No build trigger found in commit messages - skipping"
echo "should_build=false" >> $GITHUB_OUTPUT echo "should_build=false" >> $GITHUB_OUTPUT
echo "is_dry_run=false" >> $GITHUB_OUTPUT echo "is_dry_run=false" >> $GITHUB_OUTPUT
@ -112,7 +97,6 @@ jobs:
outputs: outputs:
branch: ${{ steps.config.outputs.branch }} branch: ${{ steps.config.outputs.branch }}
is_dry_run: ${{ needs.check-trigger.outputs.is_dry_run }} is_dry_run: ${{ needs.check-trigger.outputs.is_dry_run }}
alchemist_ref: ${{ steps.alchemist-ref.outputs.ref }}
steps: steps:
- name: Determine build parameters - name: Determine build parameters
id: config id: config
@ -139,48 +123,6 @@ jobs:
with: with:
ref: ${{ steps.config.outputs.branch }} ref: ${{ steps.config.outputs.branch }}
# -----------------------------------------------------------------------
# The pinned Alchemist ref. A tag or full commit SHA.
# -----------------------------------------------------------------------
- name: Read pinned Alchemist ref
id: alchemist-ref
run: |
REF_FILE="alchemist/alchemist_ref"
if [[ ! -f "$REF_FILE" ]]; then
echo "ERROR: $REF_FILE not found: the Alchemist ref must be pinned in this repo"
exit 1
fi
REF=$(grep -vE '^\s*(#|$)' "$REF_FILE" | head -n1 | tr -d '[:space:]')
if [[ -z "$REF" ]]; then
echo "ERROR: $REF_FILE is empty"
exit 1
fi
case "$REF" in
main|cooker|epicure|HEAD)
echo "ERROR: '$REF' is a branch, not a pin. Use a tag or a full commit SHA."
exit 1
;;
esac
echo "ref=$REF" >> $GITHUB_OUTPUT
echo "Alchemist pinned to: $REF"
- name: Validate version policy is present
run: |
# load_versions.sh checks for version_policy.sh at source time, before any recipe is read, and exits if it is missing. This repo therefore carries its own even if it holds only data items.
# A comment-only file is enough.
if [[ ! -f "alchemist/version_policy.sh" ]]; then
echo "ERROR: alchemist/version_policy.sh not found"
echo "The Alchemist exits at source time without it, even when no item needs a policy."
exit 1
fi
echo "Version policy present"
- name: Validate Bash scripts - name: Validate Bash scripts
run: | run: |
echo "Validating Bash scripts..." echo "Validating Bash scripts..."
@ -277,7 +219,7 @@ jobs:
;; ;;
component) component)
if [[ "$file_count" -ne 0 ]]; then if [[ "$file_count" -ne 0 ]]; then
fail "class 'component' must not declare files[]: deployment belongs to the external component installer" fail "class 'component' must not declare files[] - deployment belongs to the external component installer"
fi fi
# Store version is read from retroverse_manifest.json for both classes so the aggregator needs no class-specific logic. # Store version is read from retroverse_manifest.json for both classes so the aggregator needs no class-specific logic.
@ -385,7 +327,6 @@ jobs:
outputs: outputs:
items-json: ${{ steps.collect.outputs.items }} items-json: ${{ steps.collect.outputs.items }}
prev-json: ${{ steps.get-prev.outputs.prev }} prev-json: ${{ steps.get-prev.outputs.prev }}
prev-alchemist-ref: ${{ steps.get-prev.outputs.prev_alchemist_ref }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v6
@ -393,41 +334,6 @@ jobs:
ref: ${{ needs.configure.outputs.branch }} ref: ${{ needs.configure.outputs.branch }}
fetch-depth: 0 fetch-depth: 0
# -----------------------------------------------------------------------
# Alchemist checkout. sparse-checkout keeps the rest of the components
# repo out of the workspace, and the nested .git is harmless: alchemist.sh
# resolves REPO_ROOT from its working directory, which stays the store
# repo root, so $REPO_ROOT/$COMPONENT_NAME still points at the item folder.
# -----------------------------------------------------------------------
- name: Checkout Alchemist
uses: actions/checkout@v6
with:
repository: ${{ env.ALCHEMIST_REPO }}
ref: ${{ needs.configure.outputs.alchemist_ref }}
path: ${{ env.ALCHEMIST_PATH }}
sparse-checkout: automation-tools/alchemist
sparse-checkout-cone-mode: true
- name: Overlay store version policy
run: |
# version_policy.sh and version_pins.sh are read from the Alchemist's
# own SCRIPT_DIR, so the components repo's copies arrive with the
# checkout and must be replaced by this repo's.
if [[ ! -x "$ALCHEMIST_DIR/alchemist.sh" ]]; then
echo "ERROR: alchemist.sh not found or not executable at $ALCHEMIST_DIR"
exit 1
fi
rm -f "$ALCHEMIST_DIR/version_policy.sh" "$ALCHEMIST_DIR/version_pins.sh"
cp alchemist/version_policy.sh "$ALCHEMIST_DIR/version_policy.sh"
if [[ -f alchemist/version_pins.sh ]]; then
cp alchemist/version_pins.sh "$ALCHEMIST_DIR/version_pins.sh"
echo "Version pins overlaid"
fi
echo "Alchemist ready at ${{ needs.configure.outputs.alchemist_ref }}"
- name: Get previous release metadata - name: Get previous release metadata
id: get-prev id: get-prev
run: | run: |
@ -441,7 +347,6 @@ jobs:
if [[ -z "$PREV_RELEASE" || "$PREV_RELEASE" == "null" ]]; then if [[ -z "$PREV_RELEASE" || "$PREV_RELEASE" == "null" ]]; then
echo "No previous release found for branch ${BRANCH_NAME}" echo "No previous release found for branch ${BRANCH_NAME}"
echo "prev={}" >> $GITHUB_OUTPUT echo "prev={}" >> $GITHUB_OUTPUT
echo "prev_alchemist_ref=" >> $GITHUB_OUTPUT
exit 0 exit 0
fi fi
@ -452,15 +357,9 @@ jobs:
PREV=$(jq -c '.items | map({(.id): .}) | add // {}' retroverse.json) PREV=$(jq -c '.items | map({(.id): .}) | add // {}' retroverse.json)
echo "prev=$PREV" >> $GITHUB_OUTPUT echo "prev=$PREV" >> $GITHUB_OUTPUT
echo "Previous inventory: $(jq 'keys | length' <<< "$PREV") items" echo "Previous inventory: $(jq 'keys | length' <<< "$PREV") items"
# Tool version the previous release was built with. A change to it invalidates every reuse (see setup-matrix)
PREV_ALCHEMIST=$(jq -r '.alchemist_ref // ""' retroverse.json)
echo "prev_alchemist_ref=$PREV_ALCHEMIST" >> $GITHUB_OUTPUT
echo "Previous Alchemist ref: ${PREV_ALCHEMIST:-<unrecorded>}"
else else
echo "No retroverse.json found in previous release" echo "No retroverse.json found in previous release"
echo "prev={}" >> $GITHUB_OUTPUT echo "prev={}" >> $GITHUB_OUTPUT
echo "prev_alchemist_ref=" >> $GITHUB_OUTPUT
fi fi
- name: Check for component items - name: Check for component items
@ -470,7 +369,7 @@ jobs:
echo "found=true" >> $GITHUB_OUTPUT echo "found=true" >> $GITHUB_OUTPUT
else else
echo "found=false" >> $GITHUB_OUTPUT echo "found=false" >> $GITHUB_OUTPUT
echo "No component items: skipping build dependency install" echo "No component items - skipping build dependency install"
fi fi
- name: Install build dependencies - name: Install build dependencies
@ -487,13 +386,13 @@ jobs:
apt) apt)
sudo add-apt-repository -y ppa:flatpak/stable sudo add-apt-repository -y ppa:flatpak/stable
sudo apt update sudo apt update
sudo apt install -y flatpak flatpak-builder p7zip-full xmlstarlet bzip2 curl jq unzip sudo apt install -y flatpak flatpak-builder p7zip-full xmlstarlet bzip2 curl jq unzip zstd
;; ;;
pacman) pacman)
sudo pacman -Sy --noconfirm flatpak flatpak-builder p7zip xmlstarlet bzip2 curl jq unzip sudo pacman -Sy --noconfirm flatpak flatpak-builder p7zip xmlstarlet bzip2 curl jq unzip zstd
;; ;;
dnf) dnf)
sudo dnf install -y flatpak flatpak-builder p7zip p7zip-plugins xmlstarlet bzip2 curl jq unzip sudo dnf install -y flatpak flatpak-builder p7zip p7zip-plugins xmlstarlet bzip2 curl jq unzip zstd
;; ;;
*) *)
echo "ERROR: No supported package manager found (apt, pacman, dnf)" echo "ERROR: No supported package manager found (apt, pacman, dnf)"
@ -528,9 +427,7 @@ jobs:
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# Fingerprint covers only what the Alchemist consumes. The store # Fingerprint covers only what the Alchemist consumes. The store
# manifest and artwork are excluded: they are republished from # manifest and artwork are excluded.
# source every release, so a description or thumbnail edit must not
# invalidate an otherwise identical artifact.
# ----------------------------------------------------------------- # -----------------------------------------------------------------
fingerprint=$(git ls-tree -r HEAD -- "$item_dir" \ fingerprint=$(git ls-tree -r HEAD -- "$item_dir" \
| grep -v 'retroverse_manifest\.json$' \ | grep -v 'retroverse_manifest\.json$' \
@ -543,7 +440,7 @@ jobs:
# ----------------------------------------------------------------- # -----------------------------------------------------------------
upstream="" upstream=""
if [[ "$class" == "component" ]]; then if [[ "$class" == "component" ]]; then
upstream=$("$ALCHEMIST_DIR/alchemist.sh" \ upstream=$(automation-tools/alchemist/alchemist.sh \
-f "$item_dir/component_recipe.json" --resolve-versions) -f "$item_dir/component_recipe.json" --resolve-versions)
if [[ -z "$upstream" ]]; then if [[ -z "$upstream" ]]; then
@ -598,7 +495,7 @@ jobs:
PREV: ${{ steps.get-prev.outputs.prev }} PREV: ${{ steps.get-prev.outputs.prev }}
run: | run: |
if [[ "$PREV" == "{}" ]]; then if [[ "$PREV" == "{}" ]]; then
echo "No previous release: skipping bump check" echo "No previous release - skipping bump check"
exit 0 exit 0
fi fi
@ -615,7 +512,7 @@ jobs:
prev_fp=$(jq -r --arg i "$item" '.[$i].fingerprint // empty' <<< "$PREV") prev_fp=$(jq -r --arg i "$item" '.[$i].fingerprint // empty' <<< "$PREV")
prev_up=$(jq -r --arg i "$item" '.[$i].upstream_version // ""' <<< "$PREV") prev_up=$(jq -r --arg i "$item" '.[$i].upstream_version // ""' <<< "$PREV")
# New item: nothing to compare against # New item - nothing to compare against
if [[ -z "$prev_version" ]]; then if [[ -z "$prev_version" ]]; then
continue continue
fi fi
@ -654,42 +551,12 @@ jobs:
env: env:
CURRENT: ${{ needs.collect-metadata.outputs.items-json }} CURRENT: ${{ needs.collect-metadata.outputs.items-json }}
PREV: ${{ needs.collect-metadata.outputs.prev-json }} PREV: ${{ needs.collect-metadata.outputs.prev-json }}
ALCHEMIST_REF: ${{ needs.configure.outputs.alchemist_ref }}
PREV_ALCHEMIST_REF: ${{ needs.collect-metadata.outputs.prev-alchemist-ref }}
run: | run: |
echo "Current: $(jq 'keys | length' <<< "$CURRENT") items" echo "Current: $(jq 'keys | length' <<< "$CURRENT") items"
echo "Previous: $(jq 'keys | length' <<< "$PREV") items" echo "Previous: $(jq 'keys | length' <<< "$PREV") items"
echo "" echo ""
# --------------------------------------------------------------- entries=$(jq -c --argjson prev "$PREV" '
# A change to the pinned Alchemist invalidates every reuse.
#
# The tool is not covered by any item's fingerprint, so without
# this a release could carry some artifacts built by the new
# Alchemist and some by the old one. Rebuilding everything keeps a
# release internally consistent, at the cost of one full rebuild
# per Alchemist bump.
#
# This does NOT force version bumps: the bump check compares
# fingerprint and upstream version, neither of which the tool ref
# touches. A tool change rebuilds without demanding a bump on
# every item in the store.
# ---------------------------------------------------------------
ALCHEMIST_CHANGED=false
if [[ -n "$PREV_ALCHEMIST_REF" && "$PREV_ALCHEMIST_REF" != "$ALCHEMIST_REF" ]]; then
ALCHEMIST_CHANGED=true
echo "Alchemist ref changed: $PREV_ALCHEMIST_REF -> $ALCHEMIST_REF"
echo "Reuse disabled for this release: rebuilding every item"
echo ""
elif [[ -z "$PREV_ALCHEMIST_REF" && "$PREV" != "{}" ]]; then
ALCHEMIST_CHANGED=true
echo "Previous release recorded no Alchemist ref: rebuilding every item"
echo ""
fi
entries=$(jq -c \
--argjson prev "$PREV" \
--argjson alchemist_changed "$ALCHEMIST_CHANGED" '
to_entries | map( to_entries | map(
.value as $c | .value as $c |
($prev[.key] // {}) as $p | ($prev[.key] // {}) as $p |
@ -699,7 +566,6 @@ jobs:
class: $c.class, class: $c.class,
version: $c.version, version: $c.version,
can_reuse: ( can_reuse: (
($alchemist_changed | not) and
($p.version // "") == $c.version and ($p.version // "") == $c.version and
($p.fingerprint // "") == $c.fingerprint and ($p.fingerprint // "") == $c.fingerprint and
($p.upstream_version // "") == ($c.upstream_version // "") ($p.upstream_version // "") == ($c.upstream_version // "")
@ -733,9 +599,6 @@ jobs:
# =========================================================================== # ===========================================================================
# Build each item (or reuse from previous release) # Build each item (or reuse from previous release)
# =========================================================================== # ===========================================================================
# fail-fast is false: a store may hold hundreds of items and one malformed
# manifest should surface every other failure in the same run.
# ===========================================================================
build-items: build-items:
name: Build ${{ matrix.item }} name: Build ${{ matrix.item }}
needs: [configure, collect-metadata, setup-matrix] needs: [configure, collect-metadata, setup-matrix]
@ -754,33 +617,6 @@ jobs:
ref: ${{ needs.configure.outputs.branch }} ref: ${{ needs.configure.outputs.branch }}
fetch-depth: 0 fetch-depth: 0
- name: Checkout Alchemist
if: matrix.can_reuse == false
uses: actions/checkout@v6
with:
repository: ${{ env.ALCHEMIST_REPO }}
ref: ${{ needs.configure.outputs.alchemist_ref }}
path: ${{ env.ALCHEMIST_PATH }}
sparse-checkout: automation-tools/alchemist
sparse-checkout-cone-mode: true
- name: Overlay store version policy
if: matrix.can_reuse == false
run: |
if [[ ! -x "$ALCHEMIST_DIR/alchemist.sh" ]]; then
echo "ERROR: alchemist.sh not found or not executable at $ALCHEMIST_DIR"
exit 1
fi
rm -f "$ALCHEMIST_DIR/version_policy.sh" "$ALCHEMIST_DIR/version_pins.sh"
cp alchemist/version_policy.sh "$ALCHEMIST_DIR/version_policy.sh"
if [[ -f alchemist/version_pins.sh ]]; then
cp alchemist/version_pins.sh "$ALCHEMIST_DIR/version_pins.sh"
fi
echo "Alchemist ready at ${{ needs.configure.outputs.alchemist_ref }}"
- name: Install build dependencies - name: Install build dependencies
if: matrix.can_reuse == false if: matrix.can_reuse == false
run: | run: |
@ -795,13 +631,13 @@ jobs:
apt) apt)
sudo add-apt-repository -y ppa:flatpak/stable sudo add-apt-repository -y ppa:flatpak/stable
sudo apt update sudo apt update
sudo apt install -y flatpak flatpak-builder p7zip-full xmlstarlet bzip2 curl jq unzip sudo apt install -y flatpak flatpak-builder p7zip-full xmlstarlet bzip2 curl jq unzip zstd
;; ;;
pacman) pacman)
sudo pacman -Sy --noconfirm flatpak flatpak-builder p7zip xmlstarlet bzip2 curl jq unzip sudo pacman -Sy --noconfirm flatpak flatpak-builder p7zip xmlstarlet bzip2 curl jq unzip zstd
;; ;;
dnf) dnf)
sudo dnf install -y flatpak flatpak-builder p7zip p7zip-plugins xmlstarlet bzip2 curl jq unzip sudo dnf install -y flatpak flatpak-builder p7zip p7zip-plugins xmlstarlet bzip2 curl jq unzip zstd
;; ;;
*) *)
echo "ERROR: No supported package manager found (apt, pacman, dnf)" echo "ERROR: No supported package manager found (apt, pacman, dnf)"
@ -840,8 +676,7 @@ jobs:
echo "Reused assets downloaded" echo "Reused assets downloaded"
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Component items run through the Alchemist unchanged, so their archive # Component items run through the Alchemist unchanged.
# is byte-for-byte the shape the external component installer expects.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
- name: Build component item - name: Build component item
if: matrix.can_reuse == false && matrix.class == 'component' if: matrix.can_reuse == false && matrix.class == 'component'
@ -850,7 +685,7 @@ jobs:
echo "Building component item $ITEM..." echo "Building component item $ITEM..."
"$ALCHEMIST_DIR/alchemist.sh" -f "${{ matrix.dir }}/component_recipe.json" automation-tools/alchemist/alchemist.sh -f "${{ matrix.dir }}/component_recipe.json"
echo "Build complete" echo "Build complete"
@ -858,10 +693,6 @@ jobs:
# Data items stop before compression so each deployed file can be hashed # Data items stop before compression so each deployed file can be hashed
# while still uncompressed. The workflow then compresses exactly as the # while still uncompressed. The workflow then compresses exactly as the
# Alchemist would, so collection and verification stay class-agnostic. # Alchemist would, so collection and verification stay class-agnostic.
#
# The output directory MUST exist before invoking with -o: the Alchemist
# runs realpath on it, and export masks the failure from set -e, leaving
# WORKDIR empty and the artifact root at the filesystem root.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
- name: Build data item - name: Build data item
if: matrix.can_reuse == false && matrix.class == 'data' if: matrix.can_reuse == false && matrix.class == 'data'
@ -873,7 +704,7 @@ jobs:
ITEM_WORKDIR="$GITHUB_WORKSPACE/.build/$ITEM" ITEM_WORKDIR="$GITHUB_WORKSPACE/.build/$ITEM"
mkdir -p "$ITEM_WORKDIR" mkdir -p "$ITEM_WORKDIR"
"$ALCHEMIST_DIR/alchemist.sh" \ automation-tools/alchemist/alchemist.sh \
-f "${{ matrix.dir }}/component_recipe.json" \ -f "${{ matrix.dir }}/component_recipe.json" \
--dry-run \ --dry-run \
-o "$ITEM_WORKDIR" -o "$ITEM_WORKDIR"
@ -925,7 +756,7 @@ jobs:
fi fi
if [[ "${{ matrix.class }}" != "data" ]]; then if [[ "${{ matrix.class }}" != "data" ]]; then
echo "Component item: no files[] to cross-check" echo "Component item - no files[] to cross-check"
exit 0 exit 0
fi fi
@ -999,42 +830,19 @@ jobs:
ARCHIVE_SIZE=$(du -h "$ITEM/artifacts/$ITEM.tar.gz" | cut -f1) ARCHIVE_SIZE=$(du -h "$ITEM/artifacts/$ITEM.tar.gz" | cut -f1)
echo "Artifacts verified ($ARCHIVE_SIZE)" echo "Artifacts verified ($ARCHIVE_SIZE)"
# ---------------------------------------------------------------------
# Artwork is staged as a single item-named file rather than an artwork/
# directory, so collection in create-release cannot collide no matter how
# download-artifact lays the tree out. It runs for reused items too:
# artwork sits outside the fingerprint and is republished every release.
# ---------------------------------------------------------------------
- name: Stage artwork - name: Stage artwork
run: | run: |
ITEM="${{ matrix.item }}" ITEM="${{ matrix.item }}"
MANIFEST="${{ matrix.dir }}/retroverse_manifest.json" ART_SRC="${{ matrix.dir }}/artwork"
ART_REL=$(jq -r '.artwork // empty' "$MANIFEST") if [[ -d "$ART_SRC" ]]; then
mkdir -p "$ITEM/artifacts/artwork"
if [[ -z "$ART_REL" ]]; then cp -r "$ART_SRC/." "$ITEM/artifacts/artwork/"
echo "No artwork declared for $ITEM" echo "Artwork staged"
exit 0 else
echo "No artwork for $ITEM"
fi fi
ART_SRC="${{ matrix.dir }}/$ART_REL"
if [[ ! -f "$ART_SRC" ]]; then
echo "ERROR: artwork declared as '$ART_REL' but not found at $ART_SRC"
exit 1
fi
EXT="${ART_SRC##*.}"
if [[ "$EXT" == "$ART_SRC" ]]; then
echo "ERROR: artwork '$ART_REL' has no file extension"
exit 1
fi
mkdir -p "$ITEM/artifacts"
cp "$ART_SRC" "$ITEM/artifacts/$ITEM.artwork.$EXT"
echo "Artwork staged as $ITEM.artwork.$EXT"
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
@ -1061,6 +869,9 @@ jobs:
with: with:
ref: ${{ needs.configure.outputs.branch }} ref: ${{ needs.configure.outputs.branch }}
- name: Install zstd
run: sudo apt update && sudo apt install -y zstd
- name: Generate release tag - name: Generate release tag
id: tag id: tag
run: | run: |
@ -1104,52 +915,27 @@ jobs:
env: env:
CURRENT: ${{ needs.collect-metadata.outputs.items-json }} CURRENT: ${{ needs.collect-metadata.outputs.items-json }}
run: | run: |
# nullglob: without it, an unmatched glob passes through literally and
# the checks below report a missing SHA for a file called "*.tar.gz"
# instead of saying nothing was collected.
shopt -s nullglob
mkdir -p release-assets artwork-staging mkdir -p release-assets artwork-staging
echo "Downloaded artifact tree:" for dir in downloaded-artifacts/*-artifacts; do
find downloaded-artifacts/ -maxdepth 3 | sort | sed 's/^/ /' [[ -d "$dir" ]] || continue
echo "" item=$(basename "$dir" -artifacts)
# ------------------------------------------------------------------- cp "$dir/$item.tar.gz" release-assets/
# Collection is layout-agnostic on purpose. cp "$dir/$item.tar.gz.sha" release-assets/
#
# download-artifact may or may not place each artifact in its own
# subdirectory depending on version and pattern handling, so nothing
# here depends on the tree shape. Every file an item contributes is
# already named after that item, so a flattened download and a nested
# one both collect correctly.
# -------------------------------------------------------------------
while IFS= read -r file; do
cp "$file" release-assets/
done < <(find downloaded-artifacts/ -type f \
\( -name '*.tar.gz' -o -name '*.tar.gz.sha' -o -name '*.file_hashes.txt' \))
while IFS= read -r art; do if [[ -f "$dir/$item.file_hashes.txt" ]]; then
cp "$art" artwork-staging/ cp "$dir/$item.file_hashes.txt" release-assets/
done < <(find downloaded-artifacts/ -type f -name '*.artwork.*') fi
# Item archives only. The artwork bundle is also a .tar.gz and is if [[ -d "$dir/artwork" ]]; then
# built later in this job; excluding it by name keeps the inventory mkdir -p "artwork-staging/$item"
# check correct if these steps are ever reordered. cp -r "$dir/artwork/." "artwork-staging/$item/"
ARCHIVES=() fi
for candidate in release-assets/*.tar.gz; do
[[ "$(basename "$candidate")" == "artwork.tar.gz" ]] && continue
ARCHIVES+=("$candidate")
done done
if [[ "${#ARCHIVES[@]}" -eq 0 ]]; then
echo "ERROR: no artifacts were collected from downloaded-artifacts/"
echo "The download step produced nothing matching *.tar.gz - see the tree above."
exit 1
fi
MISSING=0 MISSING=0
for archive in "${ARCHIVES[@]}"; do for archive in release-assets/*.tar.gz; do
if [[ ! -f "${archive}.sha" ]]; then if [[ ! -f "${archive}.sha" ]]; then
echo "ERROR: Missing SHA file for $(basename "$archive")" echo "ERROR: Missing SHA file for $(basename "$archive")"
MISSING=$((MISSING + 1)) MISSING=$((MISSING + 1))
@ -1162,18 +948,10 @@ jobs:
fi fi
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Inventory completeness. # Check for inventory completeness.
#
# A failed matrix leg already fails build-items and skips this job, so
# this is not guarding against item build failures. It guards against
# an item silently never reaching release-assets. A download pattern
# that misses one, a collection bug, a matrix entry that produced no
# leg. None of those fail anything on their own, and the result would
# be a smaller inventory that every client reads as those items having
# been removed from the store.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
jq -r 'keys[]' <<< "$CURRENT" | sort > /tmp/expected_items jq -r 'keys[]' <<< "$CURRENT" | sort > /tmp/expected_items
printf '%s\n' "${ARCHIVES[@]}" \ ls release-assets/*.tar.gz \
| xargs -n1 basename \ | xargs -n1 basename \
| sed 's/\.tar\.gz$//' \ | sed 's/\.tar\.gz$//' \
| sort > /tmp/collected_items | sort > /tmp/collected_items
@ -1196,7 +974,7 @@ jobs:
if [[ "$INVENTORY_ERRORS" -gt 0 ]]; then if [[ "$INVENTORY_ERRORS" -gt 0 ]]; then
echo "" echo ""
echo "ERROR: inventory is incomplete: refusing to publish a partial store" echo "ERROR: inventory is incomplete - refusing to publish a partial store"
echo " Expected: $(wc -l < /tmp/expected_items) items" echo " Expected: $(wc -l < /tmp/expected_items) items"
echo " Collected: $(wc -l < /tmp/collected_items) items" echo " Collected: $(wc -l < /tmp/collected_items) items"
exit 1 exit 1
@ -1204,28 +982,18 @@ jobs:
echo "Inventory complete: $(wc -l < /tmp/collected_items) items" echo "Inventory complete: $(wc -l < /tmp/collected_items) items"
# ---------------------------------------------------------------------
# The bundle is a flat set of <item_id>.artwork.<ext> files, so the client
# finds an item's thumbnail by name without walking a directory tree.
# ---------------------------------------------------------------------
- name: Build artwork bundle - name: Build artwork bundle
run: | run: |
if [[ -d artwork-staging && -n "$(ls -A artwork-staging)" ]]; then if [[ -d artwork-staging && -n "$(ls -A artwork-staging)" ]]; then
tar -czf release-assets/artwork.tar.gz -C artwork-staging . tar -c -C artwork-staging . | zstd -19 -o release-assets/artwork.tar.zst
( cd release-assets && sha256sum artwork.tar.gz > artwork.tar.gz.sha ) echo "Artwork bundle built"
echo "Artwork bundle built: $(ls -1 artwork-staging | wc -l) image(s)"
else else
echo "No artwork to bundle" echo "No artwork to bundle"
fi fi
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Aggregation. Authored fields come from each retroverse_manifest.json; # Collect store metadata. Authored fields come from each retroverse_manifest.json;
# sha256, payload_size, url and file_hashes are derived here. # sha256, payload_size, url and file_hashes are derived here.
#
# file_hashes for a REUSED item are carried forward from the previous
# release. A reused item is never unpacked and rehashed. Omitting this
# would not fail the build; it would silently ship empty hash maps for
# the most stable items in the store.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
- name: Generate retroverse.json - name: Generate retroverse.json
env: env:
@ -1283,16 +1051,6 @@ jobs:
exit 1 exit 1
fi fi
# The authored 'artwork' value is a repo path the client cannot see.
# Replace it with the file's name inside artwork.tar.gz, or null
# when the item ships none.
artwork_file=""
for candidate in artwork-staging/"$item".artwork.*; do
[[ -f "$candidate" ]] || continue
artwork_file=$(basename "$candidate")
break
done
entry=$(jq -c \ entry=$(jq -c \
--arg sha "$sha" \ --arg sha "$sha" \
--argjson size "$size" \ --argjson size "$size" \
@ -1302,7 +1060,6 @@ jobs:
--arg built_on "$built_on" \ --arg built_on "$built_on" \
--arg status "$status" \ --arg status "$status" \
--arg source_id "$SOURCE_ID" \ --arg source_id "$SOURCE_ID" \
--arg artwork "$artwork_file" \
--argjson file_hashes "$file_hashes" \ --argjson file_hashes "$file_hashes" \
'. + { '. + {
source_id: $source_id, source_id: $source_id,
@ -1313,28 +1070,23 @@ jobs:
fingerprint: $fingerprint, fingerprint: $fingerprint,
built_on: $built_on, built_on: $built_on,
status: $status, status: $status,
artwork: (if $artwork == "" then null else $artwork end),
file_hashes: $file_hashes file_hashes: $file_hashes
}' <<< "$manifest") }' <<< "$manifest")
entries=$(jq -c --argjson e "$entry" '. += [$e]' <<< "$entries") entries=$(jq -c --argjson e "$entry" '. += [$e]' <<< "$entries")
done < <(jq -r 'keys[]' <<< "$CURRENT") done < <(jq -r 'keys[]' <<< "$CURRENT")
# alchemist_ref is recorded so the next release can tell whether the
# tool changed. Without it, reuse cannot be invalidated on a tool bump.
jq -n \ jq -n \
--arg schema "1" \ --arg schema "1" \
--arg source_id "$SOURCE_ID" \ --arg source_id "$SOURCE_ID" \
--arg tag "$RELEASE_TAG" \ --arg tag "$RELEASE_TAG" \
--arg built_on "$BUILD_TIMESTAMP" \ --arg built_on "$BUILD_TIMESTAMP" \
--arg alchemist_ref "${{ needs.configure.outputs.alchemist_ref }}" \
--argjson items "$entries" \ --argjson items "$entries" \
'{ '{
store_schema_version: ($schema | tonumber), store_schema_version: ($schema | tonumber),
source_id: $source_id, source_id: $source_id,
release_tag: $tag, release_tag: $tag,
built_on: $built_on, built_on: $built_on,
alchemist_ref: $alchemist_ref,
items: $items items: $items
}' > release-assets/retroverse.json }' > release-assets/retroverse.json
@ -1417,7 +1169,7 @@ jobs:
echo "Items built/validated:" echo "Items built/validated:"
jq -r 'to_entries[] | " \(.key) [\(.value.class)]: \(.value.version)"' <<< "$CURRENT" jq -r 'to_entries[] | " \(.key) [\(.value.class)]: \(.value.version)"' <<< "$CURRENT"
echo "" echo ""
echo "Dry-run complete: no release was created" echo "Dry-run complete - no release was created"
echo "==========================================" echo "=========================================="
# =========================================================================== # ===========================================================================
@ -1434,15 +1186,10 @@ jobs:
BUILD_MATRIX: ${{ needs.setup-matrix.outputs.build-matrix }} BUILD_MATRIX: ${{ needs.setup-matrix.outputs.build-matrix }}
run: | run: |
IS_DRY_RUN="${{ needs.configure.outputs.is_dry_run }}" IS_DRY_RUN="${{ needs.configure.outputs.is_dry_run }}"
RELEASE_RESULT="${{ needs.create-release.result }}"
# A tag only exists once create-release has actually published. It is
# empty on a dry run (the job is skipped) and on a failed release.
RELEASE_TAG="${{ needs.create-release.outputs.release_tag }}" RELEASE_TAG="${{ needs.create-release.outputs.release_tag }}"
RELEASE_URL="" if [[ -z "$RELEASE_TAG" ]]; then
RELEASE_TAG="(not created)"
if [[ -n "$RELEASE_TAG" && "$RELEASE_RESULT" == "success" ]]; then
RELEASE_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/${RELEASE_TAG}"
fi fi
echo "## RetroVERSE Build Summary" >> $GITHUB_STEP_SUMMARY echo "## RetroVERSE Build Summary" >> $GITHUB_STEP_SUMMARY
@ -1451,9 +1198,6 @@ jobs:
if [[ "$IS_DRY_RUN" == "true" ]]; then if [[ "$IS_DRY_RUN" == "true" ]]; then
echo "> **Dry run** - no release was created" >> $GITHUB_STEP_SUMMARY echo "> **Dry run** - no release was created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
elif [[ -n "$RELEASE_URL" ]]; then
echo "**[View release ${RELEASE_TAG}](${RELEASE_URL})**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi fi
echo "### Parameters" >> $GITHUB_STEP_SUMMARY echo "### Parameters" >> $GITHUB_STEP_SUMMARY
@ -1461,15 +1205,7 @@ jobs:
echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ needs.configure.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ needs.configure.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release tag | \`${RELEASE_TAG}\` |" >> $GITHUB_STEP_SUMMARY
if [[ -n "$RELEASE_URL" ]]; then
echo "| Release | [\`${RELEASE_TAG}\`](${RELEASE_URL}) |" >> $GITHUB_STEP_SUMMARY
elif [[ -n "$RELEASE_TAG" ]]; then
echo "| Release | \`${RELEASE_TAG}\` (publish failed) |" >> $GITHUB_STEP_SUMMARY
else
echo "| Release | (not created) |" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
if [[ -n "$BUILD_MATRIX" && "$BUILD_MATRIX" != "null" ]]; then if [[ -n "$BUILD_MATRIX" && "$BUILD_MATRIX" != "null" ]]; then

View file

@ -1 +0,0 @@
28e108e32350d518198857dc91ea2a2cb7b25529

View file

@ -1 +0,0 @@
export FREEDOOM_VERSION_POLICY="latest"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View file

@ -1,17 +0,0 @@
{
"freedoom": [
{
"source_url": "https://github.com/freedoom/freedoom/releases/download/v0.13.0/freedm-0.13.0.zip",
"source_type": "github_release",
"extraction_type": "archive",
"version": "$FREEDOOM_DESIRED_VERSION",
"assets": [
{
"type": "file",
"source": "freedm-0.13.0/freedm.wad",
"dest": "$COMPONENT_ARTIFACT_ROOT"
}
]
}
]
}

View file

@ -1,22 +0,0 @@
{
"schema_version": 1,
"id": "freedoom",
"class": "data",
"name": "FreeDOOM",
"description": "Freedoom is a complete, free content first person shooter game, based on the Doom engine.",
"version": "1.0.0",
"tags": ["rom", "freeware"],
"min_client_schema": 1,
"requires": ["component:uzdoom"],
"artwork": "artwork/thumb.png",
"files": [
{
"source": "freedm.wad",
"destination": "$roms_folder/doom/freedm.wad",
"policy": "replace"
}
]
}