#!/usr/bin/env bash
# gocache — this org's Go module and build cache, carried between runners.
#
#   gocache restore <module-dir>     # prints hit=exact|partial|miss to $GITHUB_OUTPUT
#   gocache save    <module-dir>     # writes on a trusted ref, on a miss, and nowhere else
#
# A runner is a pod that lives for one job, so a cache on its disk dies with it.
# Measured across 188 job logs: not one Go cache was restored, and 142 Go-gate
# minutes were 31 downloading modules, 69 compiling and 41 testing. A second
# pass over a warm cache compiled hanzoai/commerce's vet in 15s instead of 198s.
#
# The store, who may write, the parts and the index are bin/cache's; this script
# names what Go caches and what that is valid for.
#
# THE KEY: <owner/repo>/<go version>-<arch>/<sha256 of go.sum>. Modules are a
# function of go.sum, and a build cache is only valid for the toolchain and
# architecture that wrote it. A key that misses restores the newest key the repo
# saved (`latest`), so a go.sum bump still reuses every module that did not move.
#
# Go trusts GOMODCACHE and GOCACHE without re-hashing them, which is why only a
# trusted ref writes (bin/cache).
#
# ENV: everything bin/cache reads (HANZO_API_TOKEN, HANZO_API, CACHE_BUCKET, CACHE_PART).
set -uo pipefail

cmd="${1:-}"; dir="${2:-.}"
out="${GITHUB_OUTPUT:-/dev/null}"
skip() { echo "::notice::gocache: $*"; echo "hit=miss" >> "$out"; exit 0; }
case "$cmd" in restore|save) ;; *) echo "usage: gocache restore|save <module-dir>" >&2; exit 2 ;; esac

command -v go >/dev/null 2>&1 || skip "go is not on this runner"
[ -f "$dir/go.sum" ] || skip "$dir has no go.sum"

mod=$(go env GOMODCACHE); build=$(go env GOCACHE)
# Every later step reads the caches from exactly these directories, so a gate
# that names its own with `${GOCACHE:-…}` (hanzo-inc/cloud's do) builds into what
# was restored, and save archives what the gate built.
[ "$cmd" = restore ] && printf 'GOMODCACHE=%s\nGOCACHE=%s\n' "$mod" "$build" >> "${GITHUB_ENV:-/dev/null}"
if [ "$cmd" = save ]; then
  [ -d "$mod" ] || { echo "gocache: no module cache to save"; exit 0; }
  mkdir -p "$build"   # a gate that compiled nothing still leaves modules worth keeping
fi
# A caller that splits its gate across parallel jobs (build.yml `gates`) keeps
# one cache per share: each compiles its own part of the tree, and one key per
# job means no share finds its key already taken by another's archive.
name="$(go env GOVERSION)-$(go env GOARCH)"
[ -n "${GOCACHE_SHARE:-}" ] && name="$name-$(printf '%s' "$GOCACHE_SHARE" | sha256sum | cut -c1-8)"
exec "$(dirname "$0")/cache" "$cmd" "$name" \
  "$(sha256sum "$dir/go.sum" | cut -c1-32)" mod="$mod" build="$build"
