diff --git a/Dockerfile b/Dockerfile index 3f24a9d..34d9d4b 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 this stage, 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/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 37eef5f..7234ed5 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,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 @@ -77,9 +77,11 @@ 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 only the `lint` stage of + the `Dockerfile` (the Go format check, then 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 @@ -105,11 +107,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..3d5c4de 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 the lint stage of +# the Dockerfile, 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 --target lint -t "$image" . } main "$@"