Files
vaultik/script/cibuild
T
clawbot a99f9877d6
check / check (pull_request) Failing after 0s
Stamp Docker image version from the host; flush profiles on error exit (closes #75)
script/docker and script/cibuild compute the version (via script/version),
commit and build date on the host and pass them as build args; the
Dockerfile no longer runs git, which always returned "unknown" because
the build context excludes .git. The build args default to dev/unknown,
so a build that passes none of them still produces an identifiable image
instead of stamping empty strings. A dirty tree is reflected through
script/version's -dirty suffix.

main now exits via os.Exit(run()), so its deferred CPU/heap profile
writers flush before the process ends, and Entry returns a status code
instead of calling os.Exit. Each command ran its operation in an fx
goroutine that called os.Exit(1) on failure, discarding those profiles
and the PID-lock release; they now route the error to the return path
through one RunOperation helper. errReported keeps Entry from printing an
already-reported failure twice.

model: claude-opus-4-8
2026-09-21 19:11:02 +00:00

79 lines
3.6 KiB
Bash
Executable File

#!/bin/sh
# script/cibuild: run the CI build. This is the full gate, and it is two
# builds, in this order:
#
# Dockerfile.lint the linter, as a build step (a clean build IS a
# clean lint)
# Dockerfile `make fmt-check` and `make test` in the builder
# stage, then the product image
#
# Either one failing fails this script. Note what follows from the
# split: script/docker builds only the product image and so no longer
# lints -- this script and script/check (which runs script/lint) are the
# things that decide whether the tree is clean.
#
# Generic apart from the two Dockerfiles: the Gitea workflow runs this
# on push.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
# Both Dockerfiles key their check layers on CHECK_EPOCH, so a fresh
# value is what forces those layers to re-run: without it an
# unchanged tree replays them from cache, the checks never execute,
# and the build still exits 0. Each ARG sits immediately above the
# check RUNs, so dependency and module layers still cache. Both
# Dockerfiles also refuse to build at all when CHECK_EPOCH is empty,
# so a missing value fails loudly here rather than passing quietly.
#
# The value must be unique per invocation, not per second. `date +%s`
# is second-granular, so two concurrent invocations in the same
# second get identical epochs and the later one can be served from
# cache -- the original defect in miniature. `%N` alone does not fix
# it: busybox silently drops %N, exits 0, and hands back second
# granularity with no warning. `$$` is what makes this correct
# regardless, since concurrent invocations have different pids.
#
# Assign the epoch on its own line rather than inline in the
# argument. Under `set -eu` a command substitution that fails
# inside an argument does NOT abort the script: CHECK_EPOCH would
# become an empty string, an empty string is a constant, and a
# constant CHECK_EPOCH is exactly the cached-check false green this
# script exists to prevent -- so the guard would disarm itself and
# still exit 0. As a bare assignment, `set -e` catches a failing
# `date` and no build starts.
#
# A separate value per build, because they are separate builds: one
# `date` shared between them would still be fresh, but reusing it
# invites the two to be collapsed into a single value that is
# computed somewhere else and passed in.
epoch="$(date +%s%N)$$"
# cacheonly for the lint build: its verdict is the exit status and
# the image is never run, so exporting it is pure cost. See
# script/lint.
docker build --output=type=cacheonly \
--build-arg CHECK_EPOCH="$epoch" -f Dockerfile.lint .
# Version, commit and build date are computed here on the host, the
# same way script/docker does, and passed into the product build so
# the CI-built image reports its real source. The build context
# excludes .git (see .dockerignore), so the build cannot derive them
# itself; without these it would stamp the Dockerfile's dev/unknown
# fallbacks. VERSION comes from script/version, the source of truth
# shared with the Makefile.
version="$("$ROOT/script/version")"
commit="$(git rev-parse HEAD 2>/dev/null || echo unknown)"
commit_date="$(git show -s --format=%cs HEAD 2>/dev/null || echo unknown)"
epoch="$(date +%s%N)$$"
docker build --build-arg CHECK_EPOCH="$epoch" \
--build-arg VERSION="$version" \
--build-arg COMMIT="$commit" \
--build-arg COMMIT_DATE="$commit_date" \
.
}
main "$@"