Writing Makefiles
The Project
Assume a small project with this layout:
project/ main.c stack.c stack.h queue.c queue.h Makefile
A Complete Minimal Makefile
CC = gcc CFLAGS = -Wall -Wextra -std=c11 -g OBJS = main.o stack.o queue.o TARGET = program # Default target: build the final executable $(TARGET): $(OBJS) $(CC) $(CFLAGS) $(OBJS) -o $(TARGET) # Pattern rule: how to build any .o from its matching .c %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) .PHONY: clean
Variables: CC, CFLAGS, OBJS
Makefile variables avoid repeating the same values everywhere, and make the whole file easy to adjust in one place.
Variable | Convention | Purpose |
|---|---|---|
CC | The compiler to use | e.g. |
CFLAGS | Compiler flags | Warnings, standard version, optimization/debug info, all in one place |
OBJS | List of object files | Lets rules refer to "all the object files" without repeating the list |
Pattern Rules: %.o: %.c
Rather than writing a separate rule for main.o, stack.o, and queue.o individually, a pattern rule describes how to build any .o file from its correspondingly named .c file in one line:
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
Make matches this pattern against whichever .o file it needs (say, stack.o), infers the matching source file (stack.c), and runs the recipe.
Automatic Variables: $@ and $<
Variable | Meaning | In the pattern rule above |
|---|---|---|
$@ | The target of the current rule | The |
$< | The first dependency of the current rule | The matching |
$^ | All dependencies of the current rule | Used less often; e.g. all |
These let a single generic rule work correctly no matter which specific .o file is currently being built — Make substitutes in the real filenames each time.
The clean Target
clean: rm -f $(OBJS) $(TARGET)
Running make clean removes every generated file, letting you force a completely fresh rebuild — useful after changing compiler flags or when something seems stuck in a stale state.
.PHONY: Why It's Needed
Make normally treats a target name as the name of a file it should produce. But clean doesn't produce a file called clean — it just runs cleanup commands. If a file literally named clean ever existed in the project directory, Make would see it's already "up to date" and refuse to run the recipe at all. Declaring clean as .PHONY tells Make: "this target isn't a real file — always run its recipe when asked, regardless of what's on disk."
.PHONY: clean
Using the Makefile
make # builds "program" (the first target, so it's the default) make clean # removes all object files and the executable make program # explicitly builds the "program" target