Run lint and tests as Dockerfile phases built with --no-cache (closes #202)
check / check (push) Failing after 2s

script/check, cibuild, docker, lint, test, setup and install-precommit
are now the sneak/prompts main copies, unchanged: lint and test each
build their Dockerfile phase with --no-cache. The lint phase runs
golangci-lint from the image REPO_POLICIES.md names, with libvips-dev
from apt-get; the test phase runs the tests with a 90-second timeout;
the build stage depends on both. script/bootstrap installs the C
compiler and image libraries only with --cgo, which the test phase and
build stage pass, and refreshes the apt lists before its first apt
install. Dockerfile.lint and CHECK_EPOCH are gone, and make
docker-versioned and docker-test call the scripts. Without VERSION the
build stage still uses git describe, per issue 166.

Model: opus-5-5
This commit is contained in:
2026-10-05 01:13:27 +00:00
parent 55cf7f4fac
commit fe3063663c
13 changed files with 175 additions and 156 deletions
+38 -30
View File
@@ -1,55 +1,62 @@
# Lint stage
# Same image as Dockerfile.lint: change both pins together.
# golangci/golangci-lint:v2.12.2-alpine, 2026-08-07
FROM golangci/golangci-lint:v2.12.2-alpine@sha256:91b27804074a0bacea298707f016911e60cf0cdbc6c7bf5ccacb5f0606d18d60 AS lint
# Lint phase. script/lint builds it alone. The linter is run directly:
# `make lint` and script/lint are themselves a docker build.
# golangci/golangci-lint:v2.12.2, 2026-10-04
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
# The linter compiles every package, and govips needs the libvips
# headers for that. REPO_POLICIES.md has the lint phase install them
# itself; this image is Debian, so with apt-get rather than apk.
RUN apt-get update \
&& apt-get install -y --no-install-recommends libvips-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN golangci-lint run --config .golangci.yml ./...
# Test phase. script/test builds it alone.
# golang:1.25.4-alpine, 2026-02-25
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS test
WORKDIR /src
# script/bootstrap installs the build dependencies and downloads the Go
# modules. Only script/, go.mod and go.sum are copied first, so this
# layer is reused until one of them changes.
# script/bootstrap --cgo installs the build dependencies (a C compiler
# and the libvips and libheif headers) and downloads the Go modules.
COPY script/ ./script/
COPY go.mod go.sum ./
RUN script/bootstrap
RUN script/bootstrap --cgo
# Copy source code
COPY . .
# Tells script/lint it is inside a container, so it runs the linter.
ENV container=docker
# Without -v first; on a failure, again with -v for the details, and
# the step fails even if the second run passes.
RUN go test -count=1 -timeout 90s -race -cover ./... || \
{ echo "--- Rerunning with -v for details ---"; \
go test -count=1 -timeout 90s -race -v ./...; exit 1; }
# Run formatting check and linter. script/cibuild and script/docker pass
# a new CHECK_EPOCH on every run, and each check step names it in its
# command, so a new value reruns the step instead of reusing a cached
# success that checked nothing. A plain `docker build .` leaves it empty
# and reuses the check steps only for an identical build context.
ARG CHECK_EPOCH
RUN echo "check epoch: ${CHECK_EPOCH}" && make fmt-check
RUN echo "check epoch: ${CHECK_EPOCH}" && make lint
# Build stage
# Build stage. Nothing is wanted from the two phases above: these copies
# make BuildKit build them first, so this stage runs only when lint and
# test passed.
# golang:1.25.4-alpine, 2026-02-25
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
# Depend on lint stage passing
COPY --from=lint /src/go.sum /dev/null
COPY --from=test /src/go.sum /dev/null
WORKDIR /src
# Build dependencies and Go modules, as in the lint stage
# Build dependencies and Go modules, as in the test phase
COPY script/ ./script/
COPY go.mod go.sum ./
RUN script/bootstrap
RUN script/bootstrap --cgo
# Copy source code
COPY . .
# Run tests; a new CHECK_EPOCH reruns them, as in the lint stage.
ARG CHECK_EPOCH
RUN echo "check epoch: ${CHECK_EPOCH}" && make test
# VERSION is declared here, not earlier: a new value reruns only the
# build, not script/bootstrap or the tests. Given none, the version is
# build, not script/bootstrap. Given none, the version is
# `git describe --tags --always` of the .git in the build context (git
# comes from script/bootstrap): the tag on a tagged commit, tag-N-gHASH
# after one, the short commit when no tag is reachable. A context that
@@ -68,7 +75,8 @@ RUN version="${VERSION:-$(git describe --tags --always)}"; \
-ldflags "-s -w -X main.Version=${version}" \
-o /pixad ./cmd/pixad
# Runtime stage
# Runtime stage, and the last one: a plain `docker build .` builds this
# stage and what it depends on, and nothing else.
# alpine:3.21, 2026-02-25
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709