`--no-cache-filter=lint` is silently ignored by BuildKit when no stage matches the name, so the entire anti-false-green mechanism hung on one unvalidated magic string: renaming or mistyping the `lint` stage would have left the lint layer served from cache and `script/lint` reporting green having linted nothing. Reproduced here — with the filter pointed at a nonexistent stage and no `--target`, an unchanged tree built with `RUN golangci-lint run ... CACHED` and exited 0. `--target lint` closes it: a stage name that does not exist now fails loudly (`target stage "nosuchstage" could not be found`, exit 1) instead of passing. The two flags name the same stage from the same string and validate each other; both the script and the stage definition in Dockerfile.lint carry a comment saying they must be kept in sync. `--output=type=cacheonly` drops the image export. Nothing consumes the image — the deliverable of this build is an exit code — and the export cost seconds per run and left one dangling image behind every time, on a host where pruning is prohibited. The lint stage still executes and a lint failure still exits non-zero, both verified rather than assumed. TODO.md: the 2026-08-07 entry's claim that the repo has no linter pin and lints on the host is marked superseded in place rather than rewritten; the narrowed scaffold exemption now names `.dockerignore` alongside `Dockerfile.lint` and `script/lint`; and the specific wall-clock timings are replaced by the durable property they were evidence for, since they vary per host and per run.
39 lines
1.6 KiB
Bash
Executable File
39 lines
1.6 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/lint: run the linter. golangci-lint is never installed locally:
|
|
# it runs via docker only, one way, everywhere — script/lint builds
|
|
# Dockerfile.lint, which COPYs the repo into the pinned golangci-lint
|
|
# image and lints as a build step. This works even when the docker daemon
|
|
# is remote and bind mounts are impossible.
|
|
#
|
|
# --no-cache-filter=lint forces the lint stage to re-execute every run, so
|
|
# an unchanged tree is still actually linted; the deps stage keeps its
|
|
# cache, so the module download is not repeated.
|
|
#
|
|
# --target lint and --no-cache-filter=lint must BOTH be present, and both
|
|
# must keep naming the stage that Dockerfile.lint calls `lint`. Do not
|
|
# "simplify" either one away. BuildKit silently ignores --no-cache-filter
|
|
# when no stage matches the name: rename or typo the stage and the filter
|
|
# becomes a no-op, the lint layer is served from cache, and script/lint
|
|
# reports green having linted nothing — the exact false green this whole
|
|
# setup exists to prevent. --target fails loudly on a name that does not
|
|
# exist, so the two flags validate each other's magic string.
|
|
#
|
|
# --output=type=cacheonly skips the image export. Nothing consumes the
|
|
# image — the deliverable of this build is an exit code — and exporting it
|
|
# costs seconds per run and leaves a dangling image behind every time. The
|
|
# lint stage still executes and a lint failure still exits non-zero.
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
docker build \
|
|
--target lint \
|
|
--no-cache-filter=lint \
|
|
--output=type=cacheonly \
|
|
-f Dockerfile.lint .
|
|
}
|
|
|
|
main "$@"
|