Fail a bare docker build instead of serving cached gates (closes #39)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
Each Dockerfile stage that runs gates now checks, right after its ARG CHECK_EPOCH, that the value is not empty, and stops with a message naming script/cibuild and script/docker. A plain `docker build .` can no longer report a green from cached gate layers. script/cibuild and script/docker now append the process id to the epoch, the form script/lint already uses, so two runs started in the same second still get different values. README says both. TODO.md corrects the steady-state CACHED count recorded for issue 32 from twelve to thirteen. Model: opus-5-5
This commit was merged in pull request #93.
This commit is contained in:
+21
-6
@@ -10,7 +10,9 @@ COPY . .
|
||||
# invalidates COPY only when the copied content changes, so on an
|
||||
# unchanged tree the gates below would be served from cache and the
|
||||
# build would exit 0 having run nothing. script/cibuild and
|
||||
# script/docker pass a fresh CHECK_EPOCH on every invocation.
|
||||
# script/docker pass a fresh CHECK_EPOCH on every invocation. A build
|
||||
# that passes none, such as a bare `docker build .`, fails at the check
|
||||
# right after the ARG instead of quietly serving the gates from cache.
|
||||
#
|
||||
# Two properties this depends on. ARG is per-stage, so the markdown and
|
||||
# build stages below declare it again; one declaration here would leave
|
||||
@@ -22,6 +24,10 @@ COPY . .
|
||||
# (the pinned base image, go mod download) keeps its cache; only the
|
||||
# gates go cold.
|
||||
ARG CHECK_EPOCH
|
||||
RUN if [ -z "${CHECK_EPOCH}" ]; then \
|
||||
echo "CHECK_EPOCH is unset; build via script/cibuild or script/docker" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# The linter is invoked directly here, not through `make lint`. That
|
||||
# target now runs `docker build -f Dockerfile.lint`, and a docker build
|
||||
@@ -71,9 +77,13 @@ WORKDIR /src
|
||||
# Markdown stage: the Markdown half of `make fmt-check`, as a gate.
|
||||
FROM prettier AS markdown
|
||||
COPY . .
|
||||
# Second per-stage declaration of the gate cache-buster; see the lint
|
||||
# stage above.
|
||||
# Second per-stage declaration of the gate cache-buster and its check;
|
||||
# see the lint stage above.
|
||||
ARG CHECK_EPOCH
|
||||
RUN if [ -z "${CHECK_EPOCH}" ]; then \
|
||||
echo "CHECK_EPOCH is unset; build via script/cibuild or script/docker" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
RUN echo "gate prettier, epoch ${CHECK_EPOCH}" && \
|
||||
prettier --check '**/*.md' --tab-width 4 --prose-wrap always
|
||||
|
||||
@@ -153,10 +163,15 @@ USER builder
|
||||
# prerequisites. `make`, not the script directly, because the Makefile's
|
||||
# `export CGO_ENABLED = 0` applies only to what it invokes.
|
||||
#
|
||||
# Third per-stage declaration of the gate cache-buster; see the lint
|
||||
# stage above for why one is not enough. It is placed after USER so the
|
||||
# drop to the unprivileged user still happens before the checks run.
|
||||
# Third per-stage declaration of the gate cache-buster and its check;
|
||||
# see the lint stage above for why one is not enough. It is placed after
|
||||
# USER so the drop to the unprivileged user still happens before the
|
||||
# checks run.
|
||||
ARG CHECK_EPOCH
|
||||
RUN if [ -z "${CHECK_EPOCH}" ]; then \
|
||||
echo "CHECK_EPOCH is unset; build via script/cibuild or script/docker" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
RUN echo "gate test, epoch ${CHECK_EPOCH}" && make test
|
||||
|
||||
# The version stamped into the binary: the VERSION build argument when
|
||||
|
||||
@@ -821,9 +821,12 @@ from binary-versus-pin to pin-versus-pin, which is what
|
||||
gate layers from cache and the build exits 0 having executed no tests and no
|
||||
lint — a green it never earned, and one this repository has produced twice.
|
||||
`CHECK_EPOCH` invalidates the gate layers on every run while leaving the pinned
|
||||
base images and the dependency layers cached. `script/lint`'s value carries the
|
||||
process id as well as the epoch, because two lint runs land inside the same
|
||||
second easily and a bare epoch would cache the second one.
|
||||
base images and the dependency layers cached. Each script's value carries the
|
||||
process id as well as the epoch, because two runs land inside the same second
|
||||
easily and a bare epoch would cache the second one. Each `Dockerfile` stage with
|
||||
gates fails when the value is empty, so a bare `docker build .` stops with
|
||||
`CHECK_EPOCH is unset; build via script/cibuild or script/docker` instead of
|
||||
serving the gates from cache.
|
||||
|
||||
## Build
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- a bare `docker build .` fails with a message naming `script/cibuild` and
|
||||
`script/docker` instead of serving the gates from cache (2026-10-04,
|
||||
https://git.eeqj.de/sneak/sfdupes/issues/39)
|
||||
|
||||
- `make fmt` and `make fmt-check` run prettier over all Markdown, in Docker, and
|
||||
CI checks it; all Markdown reformatted (2026-10-04,
|
||||
https://git.eeqj.de/sneak/sfdupes/issues/19)
|
||||
@@ -298,18 +302,19 @@
|
||||
bug, not a fix. Verified by running each script twice back to back on an
|
||||
unchanged tree under `BUILDKIT_PROGRESS=plain`: all three gates executed on
|
||||
all four runs, each with a fresh epoch in the log (`script/cibuild` 78.8s then
|
||||
61.1s; `script/docker` 61.1s then 53.4s), and twelve steps were still served
|
||||
`CACHED` in the steady state — both `go mod download`s, `apk add`, `adduser`,
|
||||
the `chown`, every `go.mod`/`go.sum` and source copy, the linter copy out of
|
||||
the lint stage, and the binary copy into the runtime stage. The lint stage
|
||||
still gates the build stage: with a deliberate `unused` finding planted in the
|
||||
tree, the build failed at `make lint` in 36.1s and the build-stage
|
||||
`make check` never started. The build stage also still drops to the
|
||||
unprivileged `builder` user before `make check`, which the suite depends on
|
||||
rather than merely prefers: forcing the same image to run the tests as root
|
||||
fails `TestScanHardlinkRunFailsTogether`, because root reads straight through
|
||||
the `chmod(0)` the test uses to prove hard links are read once. This is the
|
||||
local fix only; propagating it to the canonical templates is `prompts` #26
|
||||
61.1s; `script/docker` 61.1s then 53.4s), and thirteen steps were still served
|
||||
`CACHED` in the steady state — the lint stage's `WORKDIR /src`, both
|
||||
`go mod download`s, `apk add`, `adduser`, the `chown`, every `go.mod`/`go.sum`
|
||||
and source copy, the linter copy out of the lint stage, and the binary copy
|
||||
into the runtime stage. The lint stage still gates the build stage: with a
|
||||
deliberate `unused` finding planted in the tree, the build failed at
|
||||
`make lint` in 36.1s and the build-stage `make check` never started. The build
|
||||
stage also still drops to the unprivileged `builder` user before `make check`,
|
||||
which the suite depends on rather than merely prefers: forcing the same image
|
||||
to run the tests as root fails `TestScanHardlinkRunFailsTogether`, because
|
||||
root reads straight through the `chmod(0)` the test uses to prove hard links
|
||||
are read once. This is the local fix only; propagating it to the canonical
|
||||
templates is `prompts` #26
|
||||
- check the installed golangci-lint version in `script/bootstrap` instead of
|
||||
only its presence (2026-08-09, branch `bootstrap-version-check`, closes #24):
|
||||
`missing golangci-lint` meant any linter already on `PATH` satisfied the
|
||||
|
||||
+5
-2
@@ -21,14 +21,17 @@
|
||||
# serves the gate layers from cache and the build reports a green it
|
||||
# never earned. Passing the current epoch invalidates the gate
|
||||
# layers on every run while leaving the pinned base images and
|
||||
# go mod download cached; see the Dockerfile for the placement.
|
||||
# go mod download cached; see the Dockerfile for the placement. The
|
||||
# process id goes in with the epoch so that two runs started in the
|
||||
# same second still get different values, the same form script/lint
|
||||
# uses.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build --build-arg CHECK_EPOCH="$(date +%s)" .
|
||||
docker build --build-arg CHECK_EPOCH="$(date +%s)-$$" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
# The tag comes from script/projectname.
|
||||
#
|
||||
# CHECK_EPOCH is passed for the same reason script/cibuild passes it:
|
||||
# without it Docker serves the Dockerfile's gate layers from cache on an
|
||||
# without a fresh value Docker serves the gate layers from cache on an
|
||||
# unchanged tree and this exits 0 having run none of the lint stage's
|
||||
# gates, the markdown stage's prettier gate or the builder stage's test
|
||||
# gate. This is the set of gates a developer or reviewer runs by hand,
|
||||
@@ -17,7 +17,7 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$(date +%s)" \
|
||||
--build-arg CHECK_EPOCH="$(date +%s)-$$" \
|
||||
-t "$("$SCRIPT_DIR/projectname")" \
|
||||
.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user