ci: build variants in a shell loop, not a dynamic matrix
Some checks failed
build image / build (push) Has been cancelled

The first run produced one job leg with an empty variant, and the build
rejected it. The prepare job was fine — it set variants=["minimal"] and the
output evaluated correctly — but Gitea's runner resolves strategy.matrix while
planning the workflow, before `needs` has produced anything, so
fromJSON(needs.prepare.outputs.variants) saw an empty string.

Dynamic matrices from needs outputs are not supported here. Drop the prepare
job and loop in shell instead: one job, no expression-engine dependency, and a
non-zero exit only after every requested variant has been attempted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
This commit is contained in:
2026-07-27 12:09:42 +03:00
parent 4d1fd98683
commit d168df0977

View File

@@ -13,35 +13,21 @@ on:
default: minimal default: minimal
options: [minimal, workstation, both] options: [minimal, workstation, both]
jobs: # Deliberately no strategy.matrix here.
# Which variants to build. Pushes and PRs build minimal only — it is the fast #
# one and it exercises the entire pipeline. Tags build everything. # The obvious shape for this is a `prepare` job emitting a JSON list and a
prepare: # matrix built from `fromJSON(needs.prepare.outputs.variants)`. Gitea's runner
runs-on: metal # evaluates strategy.matrix when it plans the workflow — before `needs` has run
outputs: # — so that expression resolves to an empty string and you get exactly one job
variants: ${{ steps.pick.outputs.variants }} # leg with an empty variant. The prepare job succeeds and sets its output
steps: # correctly; the matrix simply never sees it.
- id: pick #
run: | # Looping in shell is less elegant and entirely reliable.
if [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
variants='["minimal","workstation"]'
else
case "${{ inputs.variant }}" in
both) variants='["minimal","workstation"]' ;;
workstation) variants='["workstation"]' ;;
*) variants='["minimal"]' ;;
esac
fi
echo "variants=$variants" | tee -a "$GITHUB_OUTPUT"
jobs:
build: build:
needs: prepare
runs-on: metal runs-on: metal
timeout-minutes: 600 timeout-minutes: 600
strategy:
fail-fast: false
matrix:
variant: ${{ fromJSON(needs.prepare.outputs.variants) }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -79,39 +65,65 @@ jobs:
# that has not built before starts cold. # that has not built before starts cold.
- name: Prepare persistent build state - name: Prepare persistent build state
run: | run: |
echo "CACHE_DIR=/var/tmp/c630-build/dnf" >> "$GITHUB_ENV" echo "CACHE_DIR=/var/tmp/c630-build/dnf" >> "$GITHUB_ENV"
echo "WORK_DIR=/var/tmp/c630-build/work" >> "$GITHUB_ENV" echo "WORK_DIR=/var/tmp/c630-build/work" >> "$GITHUB_ENV"
mkdir -p /var/tmp/c630-build/{dnf,work} mkdir -p /var/tmp/c630-build/dnf /var/tmp/c630-build/work
# Keep it bounded: drop cached rpms nothing has touched in a month. # Keep it bounded: drop cached rpms nothing has touched in a month.
find /var/tmp/c630-build/dnf -type f -atime +30 -delete 2>/dev/null || true find /var/tmp/c630-build/dnf -type f -atime +30 -delete 2>/dev/null || true
du -sh /var/tmp/c630-build/* 2>/dev/null || true du -sh /var/tmp/c630-build/* 2>/dev/null || true
- name: Select variants
run: |
if [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
variants="minimal workstation"
else
case "${{ inputs.variant }}" in
both) variants="minimal workstation" ;;
workstation) variants="workstation" ;;
*) variants="minimal" ;;
esac
fi
echo "VARIANTS=$variants" | tee -a "$GITHUB_ENV"
- name: Build - name: Build
run: | run: |
case "${{ matrix.variant }}" in rc=0
workstation) size=16384 ;; for variant in $VARIANTS; do
*) size=8192 ;; case "$variant" in
esac workstation) size=16384 ;;
# No --fresh: the stamp in stage2.sh hashes the package lists, so a *) size=8192 ;;
# change there invalidates the staged base on its own. Checkout is esac
# shallow here anyway, so diffing against HEAD~1 would not be reliable. echo "::group::build $variant (${size} MiB)"
./build/build-image.sh \ # No --fresh: the stamp in stage2.sh hashes the package lists, so a
--variant "${{ matrix.variant }}" \ # change there invalidates the staged base on its own.
--size "$size" \ if ./build/build-image.sh \
--cache "$CACHE_DIR" \ --variant "$variant" \
--work "$WORK_DIR" --size "$size" \
--cache "$CACHE_DIR" \
--work "$WORK_DIR"; then
echo "$variant ok"
else
echo "::error::build failed for $variant"
rc=1
fi
echo "::endgroup::"
done
exit $rc
- name: Checksums - name: Checksums
run: cat output/*.sha256 if: always()
run: cat output/*.sha256 2>/dev/null || echo "no images produced"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always()
with: with:
name: fedora-${{ matrix.variant }}-lenovo-yoga-c630 name: fedora-lenovo-yoga-c630
path: | path: |
output/*.img.zst output/*.img.zst
output/*.sha256 output/*.sha256
retention-days: 14 retention-days: 14
compression-level: 0 # already zstd compression-level: 0 # already zstd
if-no-files-found: warn
- name: Attach to release - name: Attach to release
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')