5.3.6 · D3Build Systems & Toolchain

Worked examples — Makefiles — targets, prerequisites, recipes, variables, automatic variables

4,906 words22 min readBack to topic

This page is a workout. The parent note taught you the rules; here we run Make's brain by hand across every situation it can face. Before each answer you will forecast — guess out loud. That is how the rules stick.


The scenario matrix

Make's whole job is a single yes/no question per file: "is this target stale?" Everything below is just that question under different conditions. Here is every case-class we must cover.

# Case class The tricky part it tests
A Fresh build — target does not exist The "doesn't exist" branch of the rebuild rule
B Target exists, all prereqs older The "already up-to-date, do nothing" branch
C Target exists, one prereq newer Timestamp comparison picks the stale input
D Chained / recursive dependency Post-order DAG traversal — build deep before shallow
E Automatic variables in a pattern rule $@ $< $^ $? and the stem $* expanding per rule
F Variable flavor trap (= vs :=) Lazy vs eager expansion timing
G Degenerate inputs — no prereqs, .PHONY, empty recipe Edge behaviour, always-run targets
I Exam twist$? vs $^, ordering of prereqs Subtle "only the newer ones" semantics
J Pathological edges — equal timestamps, missing prereq, a cycle What bites you at the boundaries
H Word problem — incremental edit in a real project Which minimal set rebuilds

We will use a timeline picture for the timestamp cases: time flows left→right, each file is a dot placed at its modification time.

Figure s01 — the stale test on a timeline

The image below is the mental model for cases B, C, H and I. Walk it slowly:

Figure — Makefiles — targets, prerequisites, recipes, variables, automatic variables
  • The white arrow is time; further right means a newer modification time.
  • The yellow dot is the target , placed at .
  • The green dot (left, ) is a prerequisite that is older than — it sits to 's left, so it is safe: it cannot make stale.
  • The red dot (right, ) is a prerequisite that is newer than — it sits to 's right. The red arrow from to it is the trigger: any input to the right ⇒ rebuild.

So the whole page, visually, is one rule: "is any input-dot to the right of the target-dot?"


Case A — Fresh build (target absent)


Case B — Everything already up-to-date


Case C — One prerequisite is newer


Case D — Chained dependency (recursive, post-order)

main.c edited newest

main.o compile

app link last

Figure s02 — recipes fire bottom-up

Figure — Makefiles — targets, prerequisites, recipes, variables, automatic variables

Walk the picture top-to-bottom: the green node main.c is the leaf you edited (newest) — nothing to compile there. The white arrows point up the dependency chain. Make descends to the bottom, then executes upward: the blue node main.o runs its recipe first (labelled "1st recipe: compile"), and only then the yellow node app runs "2nd recipe: link." The red arrow on the left reminds you the edit that started it all lives at the leaf.


Case E — Automatic variables including the stem $*, decoded per rule


Case F — Variable flavor timing (= vs :=)


Case G — Degenerate inputs


Case I — Exam twist: $? vs $^ with mixed timestamps

Figure s04 — which prereqs land in $?

Figure — Makefiles — targets, prerequisites, recipes, variables, automatic variables

The yellow dashed line marks the target archive.a at . Each object is a dot on the timeline: a.o (green, ) sits left of the line → not newerdropped from $?; b.o () and c.o () sit right of the line (red) → newerkept in $?. The caption spells out the outcome: $^ = all three, $? = the two red ones. $? is literally "everything to the right of the yellow line."


Case J — Pathological edges (equal timestamps, missing prereq, cycles)


Case H — Word problem: minimal rebuild in a real project

Figure s03 — one edit, minimal rebuild

Figure — Makefiles — targets, prerequisites, recipes, variables, automatic variables

Read the three columns as three source→object chains all feeding the single binary app at the bottom. Only the middle column is red: you edited utils.c (red source), so its utils.o turns red (recompiled) and app turns red (relinked). The green sources main.c, math.c and their blue objects main.o, math.o stay cool — reused untouched. The red text on the sides names the two recipes that actually run; everything green/blue is the work Make skipped.


Wrap-up recall

Recall Which case did each example cover? (reveal to self-check closure)

A fresh build ::: existence branch true — target absent, so rebuild with no timestamp check B skip ::: exists AND all prereqs older — nothing to the right of the target, do nothing C single edit ::: one prereq newer flips the comparison to true — exactly one rebuild D chain ::: post-order DAG traversal — deepest recipe (compile) fires before the root (link) E pattern rule ::: $@ $< $^ and the stem $* expand per matched target F flavors ::: := snapshots early (C=1), = re-reads latest (B=D=3) G degenerate ::: empty prereqs make the ∃ clause vacuously false; .PHONY forces always-run I twist ::: $? is the subset of $^ strictly newer than the target (b.o c.o) J1 equal times ::: strict > means a tie (100 = 100) counts as up-to-date — no rebuild J2 missing prereq ::: absent AND unbuildable ⇒ fatal "No rule to make target" error J3 cycle ::: not a DAG ⇒ Make warns "Circular dependency dropped" and breaks the loop H word problem ::: exactly 2 recipes rebuild (utils.o + app) — the minimal stale set


Connections

  • Hinglish version of the parent note
  • Directed Acyclic Graphs (DAG) — why post-order (Case D) terminates and cycles (Case J3) are forbidden
  • Incremental Compilation — the minimal-rebuild payoff of Case H
  • Compilation Pipeline · Linkers and Object Files — what the recipes in E/H actually invoke
  • CMake and Build Generators — tools that generate these Makefiles for you
  • Shell Scripting Basics — recipes are shell commands