#!/usr/bin/env bash # Decide which images CI should build. Prints GITHUB_OUTPUT lines: # # images=node-agent other-image space-separated, fed straight to build-images.sh # any=true|false whether there is anything to build at all # # Space-separated rather than a JSON matrix on purpose. Gitea resolves a job's matrix # when it parses the workflow — before `needs` outputs exist — so a dynamic # `fromJSON(needs...)` matrix yields one job whose name is the raw, uninterpolated # expression. See jobparser.nameWithMatrix in the Gitea source, and # https://github.com/go-gitea/gitea/issues/28207. Looping inside one job gives an # honest job name and collapsible per-image log sections instead. # # Rules, first match wins: # 1. workflow_dispatch naming one image -> that image # 2. tag push (/vX.Y.Z) -> the image named in the tag # 3. shared build plumbing changed -> every image # 4. anything else -> images with changed files # # When the diff base is unknown (first push, force-push, shallow clone) this builds # everything. Rebuilding too much is the safe direction to fail. set -euo pipefail cd "$(dirname "$0")/.." # A change to any of these can affect how every image is built. SHARED_PATHS='^(hack/|Makefile|\.hadolint\.yaml|\.gitea/workflows/)' all_images() { local dockerfile for dockerfile in images/*/Dockerfile; do # Guards against the glob staying literal when there are no images. [ -f "$dockerfile" ] || continue basename "$(dirname "$dockerfile")" done } emit() { local names=$1 reason=$2 any=false flat # Collapse the newline-separated list onto one line for the workflow output. flat=$(printf '%s\n' "$names" | tr '\n' ' ' | sed -e 's/ */ /g' -e 's/^ //' -e 's/ $//') [ -n "$flat" ] && any=true echo "selected (${reason}): ${flat:-}" >&2 printf 'images=%s\n' "$flat" printf 'any=%s\n' "$any" exit 0 } require_image() { [ -f "images/$1/Dockerfile" ] || { echo "no such image: images/$1/Dockerfile does not exist" >&2 exit 1 } } # 1. Explicit request via workflow_dispatch. case "${DISPATCH_IMAGE:-}" in '') ;; all) emit "$(all_images)" 'workflow_dispatch: all' ;; *) require_image "$DISPATCH_IMAGE" emit "$DISPATCH_IMAGE" "workflow_dispatch: $DISPATCH_IMAGE" ;; esac # 2. Release tag: /vX.Y.Z. if [ "${GITHUB_REF_TYPE:-}" = tag ]; then image=${GITHUB_REF_NAME%/*} require_image "$image" emit "$image" "tag ${GITHUB_REF_NAME}" fi # 3 and 4 both need a usable diff base. base=${BASE_SHA:-} if [ -z "$base" ] || [[ $base =~ ^0+$ ]] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then emit "$(all_images)" 'diff base unavailable, building everything' fi changed=$(git diff --name-only "$base" HEAD) if printf '%s\n' "$changed" | grep -qE "$SHARED_PATHS"; then emit "$(all_images)" 'shared build plumbing changed' fi # Map changed paths back to image names, dropping any that no longer exist so a # deleted image directory does not fail the build. selected=$( printf '%s\n' "$changed" | sed -n 's#^images/\([^/]*\)/.*#\1#p' | sort -u | while IFS= read -r i; do [ -f "images/$i/Dockerfile" ] && echo "$i" done ) emit "$selected" 'changed paths'