Make as a Dependency Graph
Targets, prerequisites, recipes, automatic variables, and why incremental builds are correct only when dependencies are complete.
The whole model in three sentences
A Makefile states: a target depends on prerequisites, and a recipe rebuilds it when any prerequisite is newer. Make walks this graph bottom-up, skipping everything already up to date. That is the entire theory.
CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -O2
app: main.o util.o
$(CC) main.o util.o -o app
%.o: %.c util.h
$(CC) $(CFLAGS) -c $< -o $@
$< is the first prerequisite, $@ the target, %.o: %.c a pattern rule โ three pieces of syntax covering most real builds.
Incompleteness is the classic failure
If util.o's rule forgets its dependency on util.h, editing util.h rebuilds nothing โ the build succeeds while shipping stale objects. Missing dependencies do not fail loudly; they lie quietly. Header dependency generators (-MMD -MP) exist precisely because humans forget.
Order-only and phony targets
Directories as prerequisites should be order-only (| bin), and non-file targets like clean must be declared .PHONY โ or a stray file named clean silently disables it.