#!/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 .

    epoch="$(date +%s%N)$$"
    docker build --build-arg CHECK_EPOCH="$epoch" .
}

main "$@"
