diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9c7e9b3 --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +# 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)