Phony targets, pattern rules, implicit rules
1. The core model (derive it from scratch)
WHAT a rule is — the atomic unit:
target: prerequisites
recipe # MUST be a TAB, not spacesHOW Make decides to run the recipe:
WHY timestamps and not content? Because checking a timestamp is — one stat() call — while hashing file contents is . Make trades correctness-on-clock-skew for speed. This single decision drives everything below.
2. Phony targets
WHY you need .PHONY: imagine a file named clean accidentally exists in your directory.
clean:
rm -f *.oWithout .PHONY, Make sees the file clean, finds it has no prerequisites, decides it is up to date (timestamp model: missing? no. older than a prereq? no prereqs), and prints make: 'clean' is up to date. — the recipe never runs. That is the bug.
The fix — tell Make this target is always out of date:
.PHONY: clean all test
all: program
clean:
rm -f *.o program
test: program
./program --selftest3. Pattern rules
The canonical one:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@WHAT the % does, step by step, when Make wants parser.o:
- Make needs
parser.o. No explicit rule? Search pattern rules. %.omatchesparser.owith stem =parser. Why this step? The%greedily binds whatever makes the literal parts (.o) line up.- Substitute stem into prereq
%.c→parser.c. Why? The stem must be identical on both sides — that's how Make links target to source. - If
parser.cexists (or can itself be made), the rule applies.
The automatic variables (you must know these cold):
| Var | Means | In our rule for parser.o |
|---|---|---|
$@ |
the target | parser.o |
$< |
the first prerequisite | parser.c |
$^ |
all prerequisites (deduped) | parser.c parser.h … |
$* |
the stem | parser |
WHY automatic variables exist: the pattern doesn't know the concrete names yet, so Make injects them at match time. You write the rule once, generically.

4. Implicit rules
WHY: 90% of C/C++ projects compile .c → .o the same way, so GNU Make hard-codes it:
# built-in, roughly:
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@So this complete Makefile works:
program: main.o util.o
$(CC) $^ -o $@
# no %.o rule written — implicit rule builds main.o and util.o!You influence implicit rules through built-in variables: CC, CFLAGS, CPPFLAGS, LDFLAGS. That's the 80/20: set those variables, let the implicit machinery do the work.
5. Worked examples
6. Forecast-then-Verify
7. Flashcards
What makes a target "out of date" in Make?
Why does clean need .PHONY?
What does the % in a pattern rule capture?
$@, $<, $^, $* mean?
Difference between a pattern rule and an implicit rule?
%.o:%.c).Rule precedence order in Make?
Which variable selects the compiler in implicit rules?
CC (default cc); override with make CC=clang.Why timestamps instead of content hashing?
stat() timestamp check is O(1); hashing is O(filesize). Speed over clock-correctness.In %.o: %.c, building net.o, what is $*?
net (the stem).Recall Feynman: explain to a 12-year-old
Imagine LEGO instructions. Each step says "to build the roof, you need the walls first." Make is a robot that reads these. It looks at the clock sticker on each piece: if the walls got changed after the roof was built, it rebuilds the roof — otherwise it skips it to save time. Phony steps are chores like "clean up the floor" — there's no LEGO piece called "clean", so we tell the robot "this is always a chore, just do it." A pattern rule is one instruction card that works for every brick: "to make any X-brick, take its X-stick and snap it." An implicit rule is a chore the robot already knows by heart so you don't even write the card.
Connections
- Makefile variables and expansion —
:=vs=, whereCFLAGSlives. - Dependency graphs and topological build order — the DAG behind §1.
- Incremental compilation — why timestamps enable not-rebuilding-everything.
- CMake and Ninja — modern generators that emit these rules for you.
- Compiler flags CFLAGS LDFLAGS — knobs for implicit rules.
- Build reproducibility — why pinning
CCmatters.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, Make basically ek timestamp-based robot hai. Tum usse batate ho ki konsa file kis cheez par depend karta hai, aur woh sirf tab recipe chalata hai jab target file ya to exist nahi karti, ya uska koi prerequisite (source file) usse zyada naya hai. Yehi ek simple rule — "agar source newer hai to rebuild karo" — pura build system chalata hai, aur isiliye edit karne par sirf affected files rebuild hoti hain, sab kuch nahi.
Ab teen special cheezein. Phony target woh hota hai jo koi real file nahi hai — jaise clean ya all. Problem ye hai ki agar galti se clean naam ki file ban gayi, to Make sochega "file exist karti hai, kuch newer nahi hai, to up-to-date hai" aur recipe skip kar dega. Isliye .PHONY: clean likhte hain — matlab "ye file nahi, ye to kaam hai, hamesha chalao."
Pattern rule ka funda hai DRY — Don't Repeat Yourself. Agar 50 .c files hain, to 50 rules likhna pagalpan hai. Tum ek hi %.o: %.c likhte ho, aur % (stem) har naam ke liye match ho jaata hai. Andar $< matlab first prerequisite (source), $@ matlab target, $^ matlab saare prerequisites. Implicit rule matlab Make ke andar already ye %.o: %.c rule built-in hai, to kabhi-kabhi tumhe likhna bhi nahi padta — bas CC aur CFLAGS variables set karo. Precedence yaad rakho: explicit rule sabse strong, phir tumhara pattern, phir built-in implicit. Exam aur real projects dono mein yehi 80/20 hai.