Make Cheatsheet
Rules and Targets
Use this Make reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Recipe lines must start with a literal TAB
This is the number one make error. Every command line in a recipe begins with a real tab character, not spaces. Editors that expand tabs will silently break the file.
Makefile:2: *** missing separator. Stop.That message almost always means line 2 started with spaces. Spot it without guessing:
cat -A Makefile | head # GNU: tabs show as ^I sed -n 'l' Makefile | head # portable: tabs show as \t grep -nE '^ +[^ ]' Makefile # recipe lines indented with spaces
Tell your editor to leave Makefiles alone:
# .editorconfig
[Makefile]
indent_style = tab
[{Makefile,*.mk,makefile,GNUmakefile}]
indent_style = tabGNU make 3.82 and newer can change the character with .RECIPEPREFIX, but
nothing else expects it, so keep tabs.
.RECIPEPREFIX := >
Anatomy of a rule
A rule is a target, its prerequisites, and a recipe. Variables and functions are
expanded by make, then each recipe line is handed to /bin/sh as its own
process.
app: main.o util.o # target: prerequisites cc -o app main.o util.o # recipe line, starts with a TAB
- Target: usually a file make creates. If it is not a file, mark it
.PHONY.
- Prerequisites: files that must exist and be up to date first.
- Recipe: shell commands. Each line is a separate shell, so cd does not
persist to the next line unless you join with && or enable .ONESHELL.
Recipe line prefixes, which may be combined:
| Prefix | Effect |
|---|---|
@ | Do not echo the line before running it |
- | Ignore a non-zero exit status |
+ | Always run, even under make -n |
clean:
@echo "cleaning"
-rm -f app *.oThe staleness model
Make compares modification times, nothing else. A target is rebuilt when it does not exist, or when any prerequisite is newer than it. There is no content hash and no build cache.
Consequences worth remembering:
- Touching a file forces a rebuild. touch src/main.c then make proves a
dependency edge exists.
- Clock skew across a network mount or a container bind mount causes both
spurious rebuilds and, worse, skipped ones.
- A missing prerequisite edge is invisible until a stale artifact ships. Headers
are the classic hole, which is why auto-generated dependencies exist.
- Make 4.3 and newer compare with sub-second precision where the filesystem
provides it, older versions used whole seconds.
Force the issue when you need to:
make -B # unconditionally rebuild everything make -W src/x.c # pretend src/x.c was just modified make -o config.h # pretend config.h is up to date, never rebuild it make -t # touch targets instead of running recipes
Order-only prerequisites
Prerequisites after a | must exist, but their timestamps never trigger a
rebuild. This is the correct way to depend on an output directory, whose mtime
changes every time a file lands in it.
build/%.o: src/%.c | build cc -c -o $@ $< build: mkdir -p $@
Without the |, every new object file updates build/'s mtime and make rebuilds
everything on the next run. Order-only prerequisites are also excluded from $^
and $?, so cc -o $@ $^ stays correct.
Several targets, and rules with no recipe
A rule with multiple targets and a single-colon : defines the same recipe once
per target, and the recipe runs once for each.
a.out b.out: input.txt process $< $@ # runs twice, $@ differs each time
GNU make 4.3 added &: for a recipe that produces all the targets in one
invocation.
parser.c parser.h &: parser.y bison -d -o parser.c $<
A rule with prerequisites and no recipe just adds edges to an existing target. That is how you attach header dependencies without repeating the compile line.
main.o: config.h version.h
Double-colon rules :: are independent, each with its own recipe, and all of
them run. They are rare outside legacy makefiles.
A complete minimal Makefile
CC := cc CFLAGS := -std=c11 -Wall -Wextra -O2 .PHONY: all clean all: app app: main.o util.o $(CC) $(CFLAGS) -o $@ $^ main.o: main.c util.h $(CC) $(CFLAGS) -c -o $@ $< util.o: util.c util.h $(CC) $(CFLAGS) -c -o $@ $< clean: $(RM) app main.o util.o
The first non-special target in the file is the default goal, so plain make
builds all. Everything after this point is about not writing that middle
section by hand.