Files
OrcaSlicer/scripts/ci_deps_cache.sh
T

129 lines
5.0 KiB
Bash

#!/usr/bin/env bash
# Lets a pull request's macOS deps build wait for a push build of the base
# branch that builds the same deps, from a Linux job. `check` runs after a cache
# miss and names a queued or running base build with the same deps/ and .github/
# to wait for, unless the pull request changes either. `wait` waits until that
# build saves KEY. actions/cache then confirms the entry can be restored.
#
# Environment: GH_TOKEN, REPO, WORKFLOW_REF and BASE_REF, plus KEY_INPUTS,
# WAIT_FOR, WAIT_SINCE, WAIT_MINUTES, KEY and ARCH for `wait`.
# Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT.
set -uo pipefail
workflow=${WORKFLOW_REF%%@*}
workflow=${workflow##*/}
# Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml.
check_job_name="build_macos_arch (${ARCH:-}) / Check Cache"
deps_job_name="build_macos_arch (${ARCH:-}) / Build Deps / Build Deps"
out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; }
# The cache key depends on deps/ and on the workflows under .github/.
local_key_inputs() { echo "$(git rev-parse "$1:.github") $(git rev-parse "$1:deps")"; }
remote_key_inputs() {
gh api "repos/$REPO/git/trees/$1" < /dev/null \
--jq '[.tree[] | select(.path == ".github" or .path == "deps") | {key: .path, value: .sha}] | from_entries | "\(.[".github"]) \(.deps)"'
}
# Prints the id of a push build of the base branch that has not finished, is not
# one of the ids in $1, and has KEY_INPUTS.
matching_base_build() {
local status runs id sha
runs=$(for status in in_progress queued pending waiting requested; do
gh api -X GET "repos/$REPO/actions/workflows/$workflow/runs" \
-f branch="$BASE_REF" -f event=push -f status="$status" -f per_page=10 < /dev/null \
| jq -r '.workflow_runs[] | "\(.id) \(.head_sha)"'
done)
while read -r id sha; do
[ -n "$id" ] || continue
[[ " ${1:-} " == *" $id "* ]] && continue
if [ "$(remote_key_inputs "$sha")" = "$KEY_INPUTS" ]; then
echo "$id"
return
fi
done <<< "$runs"
}
check() {
KEY_INPUTS=$(local_key_inputs HEAD)
out "key-inputs=$KEY_INPUTS"
if [ "$KEY_INPUTS" != "$(local_key_inputs HEAD^1)" ]; then
echo "This pull request changes deps/ or .github/, so it builds its own deps."
return
fi
local id
id=$(matching_base_build)
if [ -n "$id" ]; then
echo "Build $id of $BASE_REF has the same deps/ and .github/. Waiting for its cache."
out "wait-for=$id"
out "wait-since=$(date +%s)"
else
echo "No build of $BASE_REF is building these deps."
fi
}
key_saved() {
gh cache list -R "$REPO" --ref "refs/heads/$BASE_REF" --key "$KEY" --json key < /dev/null \
| jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null
}
wait_for() {
# Counts the limit from the check, including time queued behind other waiting
# pull requests.
local id=$WAIT_FOR seen=$WAIT_FOR deadline=$((${WAIT_SINCE:-$(date +%s)} + WAIT_MINUTES * 60)) jobs job check warned=
while [ "$(date +%s)" -lt "$deadline" ]; do
if key_saved; then
echo "Found $KEY."
return
fi
jobs=$(gh api -X GET "repos/$REPO/actions/runs/$id/jobs" -f filter=latest -f per_page=100 < /dev/null)
job=$(jq -r --arg name "$deps_job_name" 'first(.jobs[] | select(.name == $name) | "\(.status) \(.conclusion)") // ""' <<< "$jobs")
case "$job" in
"completed success" | "completed skipped")
# The new entry can take a moment to be listed, and a skipped job means
# the build found the cache, possibly under the default branch.
for _ in 1 2 3; do
key_saved && { echo "Found $KEY."; return; }
sleep 20
done
echo "Build $id of $BASE_REF finished its deps without saving $KEY."
return ;;
"completed cancelled") ;;
completed*)
echo "The deps job of build $id of $BASE_REF did not succeed."
return ;;
# The deps job exists only once the build's cache check has succeeded.
"")
case "$(gh api "repos/$REPO/actions/runs/$id" --jq '"\(.status) \(.conclusion)"' < /dev/null)" in
"completed cancelled") ;;
completed*)
check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs")
if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then
echo "::warning title=Deps wait::Build $id of $BASE_REF has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
warned=1
fi ;;
*)
sleep 120
continue ;;
esac ;;
*)
sleep 120
continue ;;
esac
# Follows another unfinished build with the same key inputs, such as the
# push that replaced a cancelled one.
id=$(matching_base_build "$seen")
[ -n "$id" ] || break
seen="$seen $id"
echo "Following build $id of $BASE_REF."
done
echo "No build of $BASE_REF saved $KEY."
}
case "${1:-}" in
check) check ;;
wait) wait_for ;;
*) echo "usage: $0 check|wait" >&2; exit 2 ;;
esac