5.3.7Build Systems & Toolchain

Phony targets, pattern rules, implicit rules

2,191 words10 min readdifficulty · medium

1. The core model (derive it from scratch)

WHAT a rule is — the atomic unit:

target: prerequisites
	recipe       # MUST be a TAB, not spaces

HOW Make decides to run the recipe:

WHY timestamps and not content? Because checking a timestamp is O(1)O(1) — one stat() call — while hashing file contents is O(size)O(\text{size}). 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 *.o

Without .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 --selftest

3. Pattern rules

The canonical one:

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

WHAT the % does, step by step, when Make wants parser.o:

  1. Make needs parser.o. No explicit rule? Search pattern rules.
  2. %.o matches parser.o with stem = parser. Why this step? The % greedily binds whatever makes the literal parts (.o) line up.
  3. Substitute stem into prereq %.cparser.c. Why? The stem must be identical on both sides — that's how Make links target to source.
  4. If parser.c exists (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.

Figure — Phony targets, pattern rules, implicit rules

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?
It is missing, OR at least one prerequisite has a newer modification time than the target.
Why does clean need .PHONY?
So a same-named file on disk isn't treated as already-built, which would skip the recipe.
What does the % in a pattern rule capture?
The stem — the substring that, with the literal parts, matches the target; the same stem is reused in prerequisites.
$@, $<, $^, $* mean?
target, first prerequisite, all prerequisites (deduped), the stem.
Difference between a pattern rule and an implicit rule?
A pattern rule is one you write; an implicit rule is a built-in pattern rule Make already ships (e.g. %.o:%.c).
Rule precedence order in Make?
Explicit rule > your pattern rule > built-in implicit rule.
Which variable selects the compiler in implicit rules?
CC (default cc); override with make CC=clang.
Why timestamps instead of content hashing?
A 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 =, where CFLAGS lives.
  • 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 CC matters.

Concept Map

drives

checks

O 1 stat vs hashing

target missing or prereq newer

repeated 100x is verbose

solved by

solved by

breaks for non-file goals

declared with

forces

acts as

Rule: target prereqs recipe

Rebuild decision

Timestamps mtime

Speed over content-check

Run recipe

Boilerplate problem

Pattern rules %.o %.c

Implicit rules built-in

Phony targets clean all test

.PHONY marker

Timestamp +infinity

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.

Go deeper — visual, from zero

Test yourself — Build Systems & Toolchain