# sneak.berlin/go/x # # This Makefile is the single source of truth for how this repo is built, # tested, linted, and formatted. Always use these targets rather than # invoking the underlying tools directly. .PHONY: all build install test lint fmt fmt-go fmt-md fmt-check \ fmt-check-go fmt-check-md check docker hooks tidy setup clean BINARY := sutil VERSION := $(shell git describe --always --dirty 2>/dev/null || echo dev) GOLDFLAGS := -X main.version=$(VERSION) GO_DIRS := cmd internal pkg # golangci/golangci-lint:v2.12.2, 2026-05-06 # Pinned below v2.13.0 until the canonical .golangci.yml disables # exhaustruct_v5 (see the TODO in README.md). Keep in sync with Dockerfile. GOLANGCI_LINT_IMAGE := golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 # flags are repeated here (also in .prettierrc) so this Makefile works # standalone when copied as a template PRETTIER := yarn run --silent prettier PRETTIER_FLAGS := --tab-width 4 --prose-wrap always MARKDOWN_GLOB := '**/*.md' all: build # Build the sutil binary in the repo root with the git version embedded. build: go build -trimpath -ldflags "$(GOLDFLAGS)" -o $(BINARY) ./cmd/$(BINARY) # Install sutil into $(go env GOBIN) (or $GOPATH/bin). install: go install -trimpath -ldflags "$(GOLDFLAGS)" ./cmd/$(BINARY) # Run the tests quietly; on failure rerun verbosely for diagnostics and # still fail. test: @go test -timeout 30s -race -cover ./... || \ { echo "--- Rerunning with -v for details ---"; \ go test -timeout 30s -race -v ./...; exit 1; } # Lint with the pinned golangci-lint image when docker is available so local # results match CI exactly. Inside the Docker build (where there is no # docker) the image's own golangci-lint binary is used. The host module # cache is shared read-only; the linter and build caches live in named # volumes so repeated runs are fast. lint: @if command -v docker >/dev/null 2>&1; then \ docker run --rm \ -v "$(CURDIR)":/src \ -v "$$(go env GOMODCACHE)":/go/pkg/mod:ro \ -v sutil-golangci-lint-cache:/root/.cache/golangci-lint \ -v sutil-go-build-cache:/root/.cache/go-build \ -w /src $(GOLANGCI_LINT_IMAGE) golangci-lint run ./...; \ else \ golangci-lint run ./...; \ fi # Format everything (writes files). fmt: fmt-go fmt-md fmt-go: gofmt -w $(GO_DIRS) fmt-md: $(PRETTIER) --write $(MARKDOWN_GLOB) $(PRETTIER_FLAGS) # Check formatting without writing anything. fmt-check: fmt-check-go fmt-check-md fmt-check-go: @unformatted="$$(gofmt -l $(GO_DIRS))"; \ if [ -n "$$unformatted" ]; then \ echo "gofmt: the following files need formatting:"; \ echo "$$unformatted"; \ exit 1; \ fi fmt-check-md: $(PRETTIER) --check $(MARKDOWN_GLOB) $(PRETTIER_FLAGS) # Everything CI gates on. Must not modify any files. check: test lint fmt-check # Build the Docker image; the Dockerfile runs the same checks as `make check`. docker: docker build -t $(BINARY) . # Install the pre-commit hook. hooks: @printf '#!/bin/sh\nset -e\n' > .git/hooks/pre-commit @if [ -f go.mod ]; then \ printf 'go mod tidy\ngo fmt ./...\ngit diff --exit-code -- go.mod go.sum || { echo "go mod tidy changed files; please stage and retry"; exit 1; }\n' >> .git/hooks/pre-commit; \ fi @printf 'make check\n' >> .git/hooks/pre-commit @chmod +x .git/hooks/pre-commit # Tidy go.mod and go.sum. tidy: go mod tidy # One-time developer setup: install the pinned prettier for markdown checks. setup: yarn install --frozen-lockfile clean: rm -f $(BINARY)