A Makefile for the Toolchain
You now have a tool for formatting, a tool for imports, and a checker for likely bugs — each with its own command and flags. Go projects put them behind make, so nobody has to remember any of it. Open the Makefile in almost any Go repository and you'll find the same handful of target names: build, fmt, vet, clean.
.DEFAULT_GOAL := build
.PHONY: tools
tools:
go install golang.org/x/tools/cmd/goimports@latest
.PHONY: fmt
fmt:
goimports -w .
.PHONY: vet
vet:
go vet main.go
.PHONY: build
build: fmt vet
go build main.go
.PHONY: clean
clean:
rm -f mainRun make with no arguments and it does the whole chain, because .DEFAULT_GOAL points at build and build lists fmt and vet as prerequisites — make runs those first:
goimports -w .
go vet main.go
go build main.goIf vet finds something, make stops there and never reaches the build:
goimports -w .
go vet main.go
main.go:7:20: fmt.Printf format %d has arg name of wrong type string
make: *** [vet] Error 1That's the real value: one word, and a mistake can't slip past into a binary. Each target still works on its own — make fmt, make vet, make tools on a fresh machine.
Two pieces of Make worth naming:
.PHONYmarks a target that isn't a file. Make normally treats a target as a filename and skips it when that file looks up to date, so without.PHONYyourbuildtarget would quietly stop working the day a file namedbuildappeared.- Every recipe line must start with a real tab, not spaces. Make rejects the file with
missing separatorotherwise, and editors convert tabs silently. This catches everyone exactly once.
These targets name main.go directly, because commands like go vet ./... need a go.mod and you don't have one yet. Once you do, the same targets grow to cover the whole project — and staticcheck and golangci-lint, which both need a module too, drop straight in beside vet.