Dev Notes

A Makefile for Every Project

A Makefile is the universal project interface. Regardless of language, every developer understands make build and make test.

The Template

.PHONY: help build test lint clean dev

help: ## Show this help
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
		awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

build: ## Build the project
	go build -o bin/app ./cmd/app

test: ## Run tests
	go test -race -cover ./...

lint: ## Run linter
	golangci-lint run

dev: ## Run in development mode
	air -c .air.toml

clean: ## Clean build artifacts
	rm -rf bin/ tmp/

docker: ## Build Docker image
	docker build -t myapp:latest .

deploy: ## Deploy to production
	kubectl apply -f k8s/

Why This Works

  1. Self-documenting: make help lists all available commands
  2. Language-agnostic: Works for Go, Python, Node, Rust, anything
  3. Onboarding: New developers clone and run make help to get started
  4. CI/CD: Your pipeline runs make test and make build
  5. Muscle memory: Same commands across all your projects

The .PHONY Rule

Always declare phony targets. Without it, if a file named test exists in your directory, make test will say “nothing to be done”:

.PHONY: test
test:
	go test ./...

Practical Tips

  • Keep targets short (under 3 lines ideally)
  • Use variables for repeated values
  • Add a help target as the default
  • Document every target with ## comments

I have yet to find a project that does not benefit from a simple Makefile at the root.