Pattern Rules, Wildcards, and Implicit Rules
Lesson 5: Pattern Rules, Wildcards, and Implicit Rules
Writing one rule per file is madness. Pattern rules let you write one rule that matches every file of a given shape โ and Make even ships a library of implicit rules for common languages so you often don't need to write recipes at all.
Pattern Rules: One Rule, Infinite Files
A pattern rule uses % as a wildcard that matches any (possibly empty) string โ the stem:
%.o: %.c
$(CC) $(CFLAGS) -c {{LESSON_CONTENT}}lt; -o $@
This says: "any .o file can be built from the corresponding .c file." When Make needs util.o, it matches the pattern with stem util, finds util.c, and runs the recipe with $@ = util.o, {{LESSON_CONTENT}}lt; = util.c, $* = util.
The stem is available as $* โ handy when a recipe needs the name without the extension:
%.html: %.md
pandoc {{LESSON_CONTENT}}lt; -o $@ --metadata title="$*"
Wildcards and the $(wildcard) Function
Shell wildcards (*, ?, [...]) work in targets and prerequisites โ but not the way you'd expect. They're expanded by the shell inside recipes, while in the rest of the Makefile they need the $(wildcard ...) function:
# Works: wildcard function expands at parse time
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:.c=.o)
# The classic bug: this often expands to nothing in prerequisites
# app: src/*.o โ DON'T write this
$(SRCS:.c=.o) is substitution reference โ a shorthand that replaces .c with .o in every word of SRCS. The result: a list of object files that always matches your sources, automatically.
A Realistic "Auto-Growing" Makefile
Add a new .c file and this Makefile just works โ no edits needed:
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:.c=.o)
app: $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c {{LESSON_CONTENT}}lt; -o $@
clean:
rm -f app $(OBJS)
Implicit Rules: Make's Built-in Library
Make knows how to build common file types out of the box. The most famous: a .o from a .c. In fact, the pattern rule above is nearly identical to Make's built-in:
# built-in: uses CC, CFLAGS, CPPFLAGS, TARGET_ARCH
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c {{LESSON_CONTENT}}lt; -o $@
Because of that, you can often omit recipes entirely:
app: main.o util.o
$(CC) $(CFLAGS) -o $@ $^
# no %.o: %.c rule needed โ Make's implicit rule handles it
Built-in implicit rules cover .c โ .o, .cpp โ .o, .c โ executable, .tex โ .pdf, .y โ .c (yacc), .l โ .c (lex), and more. See them all with make -p.
CC, CFLAGSโฆ). That's why naming your variables CC and CFLAGS matters โ you plug into free behavior.
Automatic Dependency Generation (the Header Problem Solved)
Lesson 4's "missing dependency" bug has a clean fix. Compilers can emit a dependency file (.d) listing every header a source includes:
DEPS := $(OBJS:.o=.d)
%.o: %.c
$(CC) $(CFLAGS) -MMD -MP -c {{LESSON_CONTENT}}lt; -o $@
# pull generated .d files in as extra prerequisites
-include $(DEPS)
-MMDโ write a.dfile next to each.o, listing included headers.-MPโ add phony targets for missing headers, avoiding errors during cleanup.-includeโ include the.dfiles if they exist, without erroring on first build.
Now edit any header and Make recompiles every object that includes it โ precisely, automatically, no hand-maintained lists. This single pattern is the difference between a hobby Makefile and a professional one.
Other Graph Tools: VPATH, .DEFAULT_GOAL, .DEFAULT
VPATH = src includeโ directories Make searches for prerequisites it can't find in the current directory. Great for keeping sources separate from build output..DEFAULT_GOAL := appโ explicitly choose the default target instead of relying on "first rule wins.".DEFAULT:โ a rule run when Make needs a target with no other matching rule. Usually a debugging aid.
VPATH = src
app: main.o
$(CC) -o $@ $^
# Make finds src/main.c via VPATH and builds main.o in the current dir
๐ง Knowledge Check
1. In the pattern rule %.o: %.c building util.o, what does $* expand to?
2. What does SRCS := $(wildcard src/*.c) do?
3. Why does -include $(DEPS) use -include instead of plain include?