Stage 7 · Master
Phase 1 — Project Foundation
Makefile
Give a workspace of independent Go modules one command surface, since go test ./... from the repository root will never work in this monorepo.
Turn the Known Root Constraint Into One Reliable Command
The go.work lesson already proved why a bare root command cannot represent this repository. Do not reproduce that failure here. The new problem is operational: four valid modules now require four correctly scoped test commands, and relying on every developer to remember the current module list will drift as services are added.
Ask Go for the Workspace Modules Instead of Scanning Folders
MODULES := $(shell go list -m -f '{{.Dir}}')
.PHONY: help tidy format format-check build test vet run-organization compose-up compose-down compose-logs
help:
@echo "Modules discovered: $(MODULES)"
@echo "Targets: tidy format format-check build test vet run-organization compose-up compose-down compose-logs"
tidy:
@for m in $(MODULES); do \
echo "==> tidy $$m"; \
go mod tidy -C $$m || exit 1; \
done
format:
gofumpt -w $(MODULES)
format-check:
@test -z "$$(gofumpt -l $(MODULES))"
build:
@for m in $(MODULES); do \
echo "==> build $$m"; \
go build -C $$m ./... || exit 1; \
done
test:
@for m in $(MODULES); do \
echo "==> test $$m"; \
go test -C $$m ./... || exit 1; \
done
vet:
@for m in $(MODULES); do \
echo "==> vet $$m"; \
go vet -C $$m ./... || exit 1; \
done
run-organization:
go run -C services/organization ./cmd/api
compose-up:
docker compose up -d
compose-down:
docker compose down
compose-logs:
docker compose logs -f
Why -C, Not cd $$m &&, Runs Each Module Command
go build -C services/organization ./... tells the go command itself to change its working directory before doing anything else — equivalent to, but more explicit and less shell-quoting-fragile than, writing (cd services/organization && go build ./...) inside a Makefile recipe. The -C flag (available since Go 1.20) is what makes the loop above readable: each iteration is one line, not a subshell wrapped around a compound command.
Compose Lifecycle Targets Belong in the Same Makefile
compose-up, compose-down, and compose-logs are one-line wrappers, but putting them in the Makefile alongside the Go targets means a new contributor's entire local workflow — bring up infrastructure, build every module, run the one real service — is discoverable from a single make help, without needing to also remember plain docker compose syntax on day one.
Running Every Target Once
Applied exercise
Prove format-check fails before format repairs the file
gofumpt -l lists files that violate the repository's stricter formatting contract without modifying them.
- Deliberately misformat one file in pkg/apperr, run make format-check, and confirm it fails and names the file.
- Run make format and inspect the focused diff produced by gofumpt.
- Confirm make format-check now passes, then revert the demonstration-only change.
Deliverable
A short transcript showing format-check fail, format repair the file, and format-check pass.
Completion checks
- format-check correctly identifies the deliberately misformatted file by path.
- format-check exits 0 once gofumpt reformats the file.
- The deliberate misformatting itself is not left in any committed file.
Why does the Makefile use `go build -C $$m ./...` inside its loop instead of `cd $$m && go build ./...`?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.