tiny notes

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.