Make Cheatsheet

Pattern and Implicit Rules

Use this Make reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Pattern rules

A pattern rule uses % as a wildcard stem and applies to every target that matches. One rule replaces one hand-written rule per file. Recipe lines still start with a real tab.

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

- % matches one or more characters, including slashes, and matches the same text in the target and in every prerequisite. - The matched text is the stem, available as $*. - A pattern rule with no recipe cancels the pattern. - Multiple patterns can match. Make picks the rule whose prerequisites exist or can themselves be built, preferring the shortest stem.

%.o: %.c                        # C
%.o: %.cc                       # C++, tried when no .c exists
%.pb.go: %.proto                # multi-suffix patterns are fine
%.o: %.c generated/config.h     # extra prerequisites apply to every match
%.o: %.s                        # no recipe at all cancels a built-in

Multiple targets in one pattern rule mean the recipe produces all of them in a single run, which is the opposite of the explicit-rule behavior.

%.tab.c %.tab.h: %.y
	bison -d -o $*.tab.c $<

Static pattern rules

A static pattern rule applies a pattern to an explicit list of targets. Use it when a bare pattern rule would be too greedy or when the target list is computed.

OBJ := build/main.o build/util.o

$(OBJ): build/%.o: src/%.c
	$(CC) $(CFLAGS) -c -o $@ $<

Read it as targets: target-pattern: prereq-patterns. Differences from a plain pattern rule:

Pattern ruleStatic pattern rule
Applies toany matching targetonly the listed targets
Errors if a listed target does not match—yes, at parse time
Can be chained through intermediatesyesno
Used to build a target nobody asked foryesnever

The parse-time error is the selling point. A typo in OBJ fails immediately instead of falling through to a built-in rule.

Suffix rules, the obsolete form

Suffix rules predate patterns and are the only implicit rule POSIX make guarantees. GNU make still honors them for compatibility.

.SUFFIXES:
.SUFFIXES: .c .o

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

- A double-suffix rule .c.o means "make .o from .c". A single-suffix rule .c means "make an extension-less file from .c". - Both suffixes must be listed in .SUFFIXES or the rule is read as an ordinary target with a strange name. - Suffix rules cannot express directory changes, so build/%.o: src/%.c has no suffix-rule equivalent.

Write pattern rules. Only reach for suffix rules when the makefile must run under a non-GNU make.

Built-in rules, and turning them off

GNU make ships roughly 90 pattern rules and a matching set of default variables, which is why make foo.o works in an empty directory that contains foo.c.

gmake -p -f /dev/null | less     # print the entire built-in database
gmake -p -f /dev/null | grep -A2 '^%.o:'

The defaults they use:

VariableDefaultUsed by
CCcc%.o: %.c
CXXg++%.o: %.cc
CFLAGS CXXFLAGSemptycompile rules
CPPFLAGSemptyevery preprocessor step
LDFLAGS LDLIBSemptylink rules
RMrm -fclean recipes
AR ARFLAGSar rvarchive rules
.LIBPATTERNSlib%.so lib%.a, lib%.dylib lib%.a on macOS-lfoo prerequisites

Built-ins cause surprises: a stray foo.c next to a foo target, or an implicit RCS or SCCS lookup on every miss. Disable them for a measurable speedup and predictable behavior.

MAKEFLAGS += --no-builtin-rules --no-builtin-variables
.SUFFIXES:
gmake -r -R     # same thing from the command line

Doing this means $(CC) and $(CFLAGS) are now undefined, so define every variable your rules use.

VPATH and vpath

Make looks for prerequisites in the current directory only. VPATH adds a search path, and lowercase vpath scopes it to a pattern.

VPATH = src:src/net:include        # colon or space separated, all file types
vpath %.c src                      # only .c files
vpath %.h include generated        # only .h files
vpath                              # clear every vpath entry

- When make finds a prerequisite through the path, $< and $^ hold the found path, so $(CC) -c $< works unchanged. - Targets are still written to the current directory unless the recipe says otherwise. Make does not rewrite $@. - A target found through VPATH is only reused if it is up to date, otherwise make rebuilds it locally, which can leave two copies.

vpath is the narrow, predictable form. Large projects tend to skip both and compute explicit paths with $(wildcard ...) instead, because a search path that silently picks up a stale file in another directory is hard to debug.

Chained rules and intermediate files

Make will chain implicit rules to reach a target, inventing the middle steps.

%.mid: %.in
	cp $< $@
%.out: %.mid
	cp $< $@

Running make a.out with only a.in on disk produces:

cp a.in a.mid
cp a.mid a.out
rm a.mid

The middle file is intermediate: created, used, then deleted, and its timestamp does not trigger rebuilds. Control that with special targets.

TargetEffect
.SECONDARY: a.midKeep the intermediate. With no prerequisites, keep all
.PRECIOUS: %.midDo not delete on interrupt or error
.INTERMEDIATE: xForce a normally-permanent file to be intermediate
.NOTINTERMEDIATE:GNU make 4.4, opt out of the whole mechanism

Chains are only searched for implicit rules, never for explicit ones, and never for static pattern rules. If a two-step build is not happening, that is usually why.

Gotchas

- A pattern rule whose prerequisite does not exist is silently skipped, and make falls through to the next candidate or reports "No rule to make target". Check with gmake -d 2>&1 | grep -i 'trying pattern'. - % in a variable is not special. $(SRC:%.c=%.o) is substitution reference syntax, which is a different feature from a pattern rule. - Directory patterns must match exactly. %.o: %.c will not find src/main.c for build/main.o, because the stem would have to be both build/main and src/main. - The stem includes directories. For build/net/tcp.o, $* is net/tcp, so $(notdir $*) is what you usually want in messages. - Empty recipes matter. %.o: %.c ; cancels the built-in rule, while omitting the recipe entirely cancels the pattern. - A pattern rule cannot be .PHONY. Phony targets are matched literally, so mark the concrete names.