From 7dcae7b4718bdd82d2d6eacdadca2fb9580715c9 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 4 Oct 2026 03:35:13 +0000 Subject: [PATCH] Run all linting in Docker via Dockerfile.lint (closes #90) script/lint now only builds the new Dockerfile.lint, which copies the repo into the pinned golangci-lint image and runs the linter as a build step, so a successful build is a clean lint. It builds with --no-cache, because a cached build runs no linter, and removes the image it tagged when it exits. The main Dockerfile lint stage calls golangci-lint directly, since make lint now needs Docker. Nothing installs or runs golangci-lint on the host any more: bootstrap and the Makefile drop the install, and script/fmt drops golangci-lint run --fix. The gofmt check script/lint repeated stays in script/fmt-check. Model: opus-5-5 --- Dockerfile | 4 +++- Dockerfile.lint | 10 ++++++++++ Makefile | 3 --- README.md | 22 +++++++++++----------- script/bootstrap | 7 +------ script/fmt | 1 - script/lint | 19 +++++++++++-------- 7 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 Dockerfile.lint diff --git a/Dockerfile b/Dockerfile index 860967f..9f3c4ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,9 @@ RUN touch mfer/mf.pb.go # Go half of fmt-check only: this image has no node, so no prettier. The # markdown half runs in the mdfmt stage below. RUN make fmt-check-go -RUN make lint +# The linter directly, not `make lint`: script/lint builds Dockerfile.lint, +# and there is no docker inside this build. +RUN golangci-lint run --config .golangci.yml ./... # Markdown/JSON format stage — prettier needs node, which the Go images # do not have. node:22.17.0-bookworm-slim (2026-08-09); ships node diff --git a/Dockerfile.lint b/Dockerfile.lint new file mode 100644 index 0000000..9b5c9af --- /dev/null +++ b/Dockerfile.lint @@ -0,0 +1,10 @@ +# Lint image, built by script/lint: golangci-lint runs as a build step, so a +# successful build is a clean lint. Works where the docker daemon is remote +# and bind mounts are impossible. +# golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07 +FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 + +WORKDIR /src +COPY . . + +RUN golangci-lint run --config .golangci.yml ./... diff --git a/Makefile b/Makefile index ad56b57..38d19df 100644 --- a/Makefile +++ b/Makefile @@ -58,9 +58,6 @@ fmt-check-md: hooks: @script/install-precommit -devprereqs: - which golangci-lint || go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 - mfer/mf.pb.go: mfer/mf.proto cd mfer && go generate . diff --git a/README.md b/README.md index 3d02f75..81e6273 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ standard: normalized scripts in `script/` are the entrypoints for the development workflow, and the Makefile targets are thin shims that call them. We provide: -- `script/bootstrap` — install all dependencies (Go, golangci-lint, Go module - download, and node/yarn plus the prettier version pinned in - `package.json`/`yarn.lock`), idempotently +- `script/bootstrap` — install all dependencies (Go, Go module download, and + node/yarn plus the prettier version pinned in `package.json`/`yarn.lock`), + idempotently; golangci-lint is not installed, it runs only in Docker - `script/setup` — make a fresh clone ready for development: runs `script/bootstrap`, then `script/install-precommit` - `script/projectname` — output the project name (`mfer`); used by other scripts @@ -47,9 +47,10 @@ provide: - `script/fuzz` — fuzz the manifest parser for one minute; run by hand (`make fuzz`), never by CI, while `script/test` runs its committed seed corpus as ordinary tests -- `script/lint` — run `golangci-lint` and verify `gofmt` cleanliness -- `script/fmt` — format all code and docs (writes): `gofumpt`, - `golangci-lint run --fix`, and `script/prettier --write` +- `script/lint` — run `golangci-lint` in Docker: builds `Dockerfile.lint`, whose + build runs the linter, uncached so it runs every time, then removes the image +- `script/fmt` — format all code and docs (writes): `gofumpt` and + `script/prettier --write` - `script/prettier` — run prettier over the repository's canonical file set (Markdown and JSON, minus `.prettierignore`) in the given mode, `--write` or `--check`; the single definition of that file set, so `script/fmt` and @@ -75,11 +76,10 @@ yet. Primary development happens on a privately-run Gitea instance at [tracked there](https://git.eeqj.de/sneak/mfer/issues). Changes must always be formatted with a standard `go fmt`, syntactically valid, -and must pass the linting defined in the repository (presently only the -`golangci-lint` defaults), which can be run with a `make lint`. The `main` -branch is protected and all changes must be made via -[pull requests](https://git.eeqj.de/sneak/mfer/pulls) and pass CI to be merged. -Any changes submitted to this project must also be +and must pass the linting defined in the repository's `.golangci.yml`, which +`make lint` runs in Docker. The `main` branch is protected and all changes must +be made via [pull requests](https://git.eeqj.de/sneak/mfer/pulls) and pass CI to +be merged. Any changes submitted to this project must also be [WTFPL-licensed](https://wtfpl.net) to be considered. See [`REPO_POLICIES.md`](REPO_POLICIES.md) for detailed coding standards, diff --git a/script/bootstrap b/script/bootstrap index 2f6ef3e..1ee831f 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -140,12 +140,7 @@ main() { # ---- Go repos ---- if missing go; then pkg_install go golang go go; fi - # golangci-lint: packaged in nix, brew, and apk. On apt there is no - # package: download a specific release archive from GitHub and - # verify its hash (verify_sha256), never curl | sh. - if missing golangci-lint; then - pkg_install golangci-lint golangci-lint golangci-lint golangci-lint - fi + # No golangci-lint: script/lint runs it in Docker only. go mod download # ---- Python repos ---- diff --git a/script/fmt b/script/fmt index d4d25c8..b9aa31d 100755 --- a/script/fmt +++ b/script/fmt @@ -19,7 +19,6 @@ main() { cd "$ROOT" ensure_pb gofumpt -l -w mfer internal cmd - golangci-lint run --fix # Markdown and JSON, over the same file set script/fmt-check verifies. "$SCRIPT_DIR/prettier" --write } diff --git a/script/lint b/script/lint index 071cb67..15afff3 100755 --- a/script/lint +++ b/script/lint @@ -1,17 +1,20 @@ #!/bin/sh -# script/lint: run the linter. +# script/lint: run golangci-lint, in Docker only. Builds Dockerfile.lint, +# whose build runs the linter, so a successful build is a clean lint. +# --no-cache because a cached build runs no linter. The image is removed +# afterwards, whatever the outcome. set -eu -ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - golangci-lint run - if [ -n "$(gofmt -l .)" ]; then - echo "gofmt: files need formatting:" >&2 - gofmt -l . >&2 - exit 1 - fi + # Tagged per run, so concurrent runs never remove each other's image. + image="$("$SCRIPT_DIR/projectname")-lint:$$" + # A failed build leaves no image, so there is nothing to remove then. + trap 'docker image rm "$image" >/dev/null 2>&1 || true' EXIT INT TERM + docker build --no-cache -f Dockerfile.lint -t "$image" . } main "$@"