Three Makefile patterns I use in every project
I keep rewriting the same handful of lines, so here they are in one place.
1. Self-documenting help target
Comments after a target with a double hash become the help text:
.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf " %-16s %s\n", $$1, $$2}'
build: ## Compile everything into ./dist
test: ## Run the unit tests
Make the help target first so a bare make prints it instead of building something by accident.
2. Order-only prerequisites for directories
The pipe is the important character here:
dist/%.o: src/%.c | dist
$(CC) $(CFLAGS) -c $< -o $@
dist:
mkdir -p $@
Without the pipe, the directory's mtime counts as a dependency, and every file written into it marks your targets out of date. You end up rebuilding the world on every run and blaming the compiler.
3. Delete on error
.DELETE_ON_ERROR:
One line, no arguments. If a recipe fails halfway through writing its target, make removes the partial file. Without it you are left with a truncated output that looks up to date, and the next build happily links against garbage. I have lost more time to this than to any actual bug.
Bonus: MAKEFLAGS += --warn-undefined-variables catches the typo where $(CLFAGS) silently expands to nothing.