Pattern rules are how a Makefile scales from two files to two hundred without growing.
A pattern rule uses % to match any non-empty stem:
build/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@To build build/net.o, make matches the stem net, looks for src/net.c and runs the recipe with $@ and $< filled in. One rule now covers every source file.
Combine it with variables listing the objects, usually computed from the sources:
SRC := $(wildcard src/*.c)
OBJ := $(SRC:src/%.c=build/%.o)make also has built-in implicit rules. It already knows how to make x.o from x.c with $(CC) $(CPPFLAGS) $(CFLAGS) -c, and how to link x from x.o. That's why a Makefile containing only app: app.o can work. make -p prints all of them.
Built-in rules are convenient for tiny projects but surprising in bigger ones, and they slow make down by trying many candidate rules. Many projects disable them with MAKEFLAGS += --no-builtin-rules and an empty .SUFFIXES: line, and write explicit pattern rules instead.
Static pattern rules restrict a pattern to a list of targets: $(OBJ): build/%.o: src/%.c. They're more explicit and fail loudly when a source is missing.
Example
CC := cc
CFLAGS := -Wall -O2
SRC := $(wildcard src/*.c)
OBJ := $(SRC:src/%.c=build/%.o)
MAKEFLAGS += --no-builtin-rules
.SUFFIXES:
build/app: $(OBJ)
$(CC) -o $@ $^
build/%.o: src/%.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -rf build
# with src/main.c and src/util.c:
# $ make
# cc -Wall -O2 -c src/main.c -o build/main.o
# cc -Wall -O2 -c src/util.c -o build/util.o
# cc -o build/app build/main.o build/util.oRemember
- %.o: %.c matches any stem; $< and $@ fill in the names
- Derive object lists from sources with $(SRC:src/%.c=build/%.o)
- make has built-in rules; --no-builtin-rules makes builds explicit
Watch out for
- A pattern rule silently not applying because the prerequisite path is wrong; make then reports 'No rule to make target'.