GNU make starters, one move per page
Twenty-seven steps for people who type make daily and trust it blindly: the rule shape with its famous tab, the timestamps that decide what rebuilds, the first target as the front door, phony names like clean and all that aren't files, $@ and $< and friends that let a rule name itself, one % pattern rule doing the work of twenty, static patterns, variables defined once, wildcards under two masters, the @ and - per-line signs, -n the rehearsal, -k the survey, -j the job slots, recursive $(MAKE) -C, and the whole tool on one page, one move per page.
A diagram, the classic trap, and one thing to go try this week. That's a page.
GNU make starters, one move per page
Twenty-seven steps for people who type make daily and trust it blindly: the rule shape with its famous tab, the timestamps that decide what rebuilds, the first target as the front door, phony names like clean and all that aren't files, $@ and $< and friends that let a rule name itself, one % pattern rule doing the work of twenty, static patterns, variables defined once, wildcards under two masters, the @ and - per-line signs, -n the rehearsal, -k the survey, -j the job slots, recursive $(MAKE) -C, and the whole tool on one page, one move per page.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
GNU make behaviour checked against the GNU Make Manual (gnu.org/software/make/manual, make.info from the GNU make 4.4.1 release), fetched and read during this build, with behaviours spot-checked live against GNU Make 4.3. Flags, defaults, limits and edge cases are named as the manual names them. Teaching conventions (one move a page) are named as conventions. This book quotes no verbatim passages and is an independent guide not affiliated with or endorsed by the GNU Project or the Free Software Foundation.
General information only. Not professional advice; verify against your own make and the current documentation.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
The model
What make is underneath: the one rule shape with its famous tab, the modification times that decide what rebuilds, the first target as the front door, and the graph that orders it all.
- 01The rule, whole
- 02Timestamps decide
Per the GNU Make Manual 2.1 What a Rule Looks Like: a simple makefile consists of rules with the shape target: prerequisites, then a recipe, and you need to put a tab character at the beginning of every recipe line. A target is usually the name of a file that is generated by a program; a prerequisite is a file that is used as input to create the target.
The rule, whole
Let's say every Makefile you've read looked like noise. There's one shape underneath: target, colon, prerequisites, recipe.
The target is usually a file the recipe builds. The prerequisites are the files it's built from. The recipe is shell commands, and the manual is blunt about the catch: you need a tab at the beginning of every recipe line. That one obscurity, the manual's own word, catches the unwary.
Three lines and a tab. Every Makefile you'll ever read is made of these.
Write a three line rule for a file you actually build this week, and run make on it.
Per the GNU Make Manual 4.2 Rule Syntax: a target is out of date if it does not exist or if it is older than any of the prerequisites, by comparison of last-modification times. The idea is that the contents of the target file are computed based on information in the prerequisites.
Timestamps decide
Let's say you run make twice and the second time says 'up to date'. What was checked?
Times, nothing else. A target is out of date when it doesn't exist, or when any prerequisite is newer, compared by last-modified time. make never reads the contents to decide. The reasoning in the manual: the target is computed from the prerequisites, so a changed prerequisite makes the existing target's contents no longer necessarily valid.
It compares labels on the boxes, not what's inside them.
Build something with make, touch one source file, and rerun. Watch only that branch rebuild.
Per the GNU Make Manual 2.1: you need a put a tab character at the beginning of every recipe line; this is an obscurity that catches the unwary. If you prefer another prefix you can set the .RECIPEPREFIX variable. Live GNU Make 4.3 prints: missing separator (did you mean TAB instead of 8 spaces?).
The tab
Let's say make refuses with 'missing separator'. Count your editor's indent characters.
Every recipe line must begin with a real tab. Eight spaces look identical to you and are a different byte to make, and modern versions even guess at your mistake in the error. The one escape is setting .RECIPEPREFIX to another character, for the editors and the stubborn. Fix the editor to emit tabs, once, and this error never comes back.
The most famous wart in the tool, and it's one settings screen.
Set your editor to show whitespace this week and convert any Makefile you own to tabs.
Per the GNU Make Manual 4.6 Phony Targets: a phony target is one that is not really the name of a file, just a name for a recipe. If a file named clean is ever created, clean would always be considered up to date and its recipe would not be executed. Declare it phony by making it a prerequisite of .PHONY, then make clean runs the recipe regardless.
.PHONY
Let's say a file named clean ever lands in the directory. make clean now refuses: it sees the file, no prerequisites, up to date.
The fix is declaring the name: .PHONY: clean makes it a prerequisite of a special target, and the recipe runs regardless of any file. Phony means not-a-file, an action wearing a target's clothes. It's also a small speedup, the manual notes: implicit rule search is skipped for phony targets.
If the recipe doesn't build a file with the target's name, that target is phony. Say so.
List every action target in a Makefile you own and add one .PHONY line for all of them.
Per the GNU Make Manual 9.3 Instead of Executing Recipes: -n, --just-print, --dry-run causes make to print the recipes needed to make the targets up to date but not actually execute them; some recipes are still executed even with this flag, such as recipes using $(MAKE). -t marks targets as up to date by updating modified times. -q silently checks, with exit status zero if up to date, one if updates are needed, two on error.
-n, the rehearsal
Let's say the build might delete or overwrite something you care about. Read the plan first.
-n prints every recipe it would run, executing nothing, the same rehearsal habit as rsync's dry run. Its siblings: -t marks targets fresh by touching times, and -q says nothing and answers by exit code, 0 up to date, 1 needs work, 2 error, made for scripts. One caveat the manual flags: lines involving $(MAKE) still run under -n, recursion must recurse.
Destructive target, or borrowed Makefile? -n before enter.
Run make -n against an unfamiliar Makefile this week and read the whole plan before trusting it.