Makefiles
Once a program has several source files, typing the compile commands by hand becomes slow and error-prone. Did you recompile bill.c after changing bill.h? Did you forget -Wall this time? The classic tool that solves this is make. You describe how the program is built in a file called Makefile, and make runs exactly the commands needed, and only those.
In this lesson you will write a Makefile for the billing program from Header Files & Multi-file Programs, see how make decides what to rebuild, and improve the Makefile with variables, pattern rules and automatic header dependencies.
Installing and running make
On Linux and WSL, make comes with build-essential. On macOS it comes with the Command Line Tools. On Windows with MSYS2, the base-devel package provides make inside the MSYS2 UCRT64 terminal, and the toolchain also provides the same program as mingw32-make.exe for use in PowerShell. Check it with:
make --versionWhen you type make in a folder, it reads the file named Makefile in that folder and builds the first target it describes.
Your first Makefile
A Makefile is a list of rules. Each rule has a target (the file to build), its prerequisites (the files it is built from) and a recipe (the commands that build it):
target: prerequisites
recipe commandThe recipe line must start with a tab character, not spaces. This is the most famous quirk of make. Here is a complete Makefile for the billing program, with main.c, bill.c and bill.h in the same folder:
shop: main.o bill.o
gcc main.o bill.o -o shop
main.o: main.c bill.h
gcc -std=c23 -Wall -Wextra -c main.c
bill.o: bill.c bill.h
gcc -std=c23 -Wall -Wextra -c bill.c
clean:
rm -f shop main.o bill.oRead it from the top: to build shop, make first needs main.o and bill.o. To build main.o, it needs main.c and bill.h, and so on. Running make in a fresh folder prints each command before running it:
makegcc -std=c23 -Wall -Wextra -c main.c gcc -std=c23 -Wall -Wextra -c bill.c gcc main.o bill.o -o shop
How make decides what to rebuild
make compares modification times. A target is rebuilt only if it does not exist or if any prerequisite is newer than it. Run make again straight away and nothing needs doing:
makemake: 'shop' is up to date.
Now edit bill.c (or use touch bill.c, which just updates its time) and run make once more:
touch bill.c
makegcc -std=c23 -Wall -Wextra -c bill.c gcc main.o bill.o -o shop
Only bill.o was rebuilt, then the program was linked again. main.o was still newer than main.c and bill.h, so make skipped it. If you change bill.h, both object files are rebuilt, because both rules list bill.h as a prerequisite. This is why listing headers correctly matters: a missing header prerequisite means stale object files and confusing bugs.
make clean runs the clean rule, which deletes the generated files so the next build starts fresh. You can name any target on the command line: make bill.o builds just that file.
Variables and automatic variables
Repeating gcc -std=c23 -Wall -Wextra in every rule invites mistakes. Makefile variables hold such text once. By convention, CC names the C compiler and CFLAGS holds the compiler options. Rules can also use automatic variables, which make fills in for each rule:
| Variable | Meaning |
|---|---|
$@ | The target of the rule |
$< | The first prerequisite |
$^ | All the prerequisites |
With these, a pattern rule written with % can describe how to build any .o file from the .c file with the same name:
CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -g
OBJS = main.o bill.o
shop: $(OBJS)
$(CC) $^ -o $@
%.o: %.c bill.h
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f shop $(OBJS)make clean
makerm -f shop main.o bill.o gcc -std=c23 -Wall -Wextra -g -c main.c -o main.o gcc -std=c23 -Wall -Wextra -g -c bill.c -o bill.o gcc main.o bill.o -o shop
To add a new file such as storage.c, you now only add storage.o to OBJS. To build with different options for one run, override a variable on the command line: make CFLAGS="-std=c23 -O2". If your program uses math.h, add LDLIBS = -lm and put $(LDLIBS) at the end of the link command.
Phony targets
clean is not a file; it is just a name for a task. .PHONY: clean tells make so. Without it, if a file called clean ever appeared in the folder, make would think the target was up to date and refuse to run the recipe. Other common phony targets are all (build everything, usually listed first) and run:
.PHONY: all run clean
all: shop
run: shop
./shopAutomatic header dependencies
In a large project, keeping every header list correct by hand is impossible. GCC and Clang can write the dependency lists for you. The options -MMD -MP make the compiler produce a small .d file next to each .o, listing the headers that file really included, and make can read those files:
CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -g -MMD -MP
OBJS = main.o bill.o
shop: $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f shop $(OBJS) $(OBJS:.o=.d)
-include $(OBJS:.o=.d)$(OBJS:.o=.d) turns the list main.o bill.o into main.d bill.d. The - before include means "do not complain if the files do not exist yet", which is the case on the very first build. From then on, any header change triggers exactly the right rebuilds. Add -j to build several files in parallel: make -j4 runs up to four commands at once.
Common mistakes
- Spaces instead of a tab. make stops with
*** missing separator. Stop.and the line number. Set your editor to keep real tabs in Makefiles; VS Code does this automatically for files namedMakefile. - Missing header prerequisites. Changes to a header do not trigger rebuilds. List headers or use
-MMD -MP. - Forgetting
.PHONY. A file named like a task, such asclean, stops that task from running. - Wrong folder.
make: *** No targets specified and no makefile found. Stop.means there is noMakefilewhere you ran the command. - Assuming the output is always rebuilt. If nothing changed, make does nothing. Use
make cleanfollowed bymaketo force a full rebuild.
Key points
- A Makefile is a list of rules:
target: prerequisites, then recipe lines that start with a tab. - make rebuilds a target only when it is missing or older than one of its prerequisites.
- Variables such as
CCandCFLAGSkeep options in one place;$@,$<and$^stand for the target and prerequisites. - A pattern rule
%.o: %.cbuilds every object file the same way. - Mark task names such as
clean,allandrunas.PHONY. -MMD -MPwith-includekeeps header dependencies correct automatically.
Exercise
Write a Makefile for the temperature converter from the previous lesson (main.c, convert.c, convert.h). Use CC, CFLAGS and an OBJS variable, a pattern rule for object files, a link rule for a program called convert, and phony run and clean targets. Then write the commands you would type to build and run it.
Show solution
CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -g
OBJS = main.o convert.o
convert: $(OBJS)
$(CC) $^ -o $@
%.o: %.c convert.h
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: run clean
run: convert
./convert
clean:
rm -f convert $(OBJS)make rungcc -std=c23 -Wall -Wextra -g -c main.c -o main.o gcc -std=c23 -Wall -Wextra -g -c convert.c -o convert.o gcc main.o convert.o -o convert ./convert 37.0 C = 98.6 F 212.0 F = 100.0 C
make run first builds convert, because run depends on it, then runs it. make prints each command, including ./convert, before the program's own output. Every recipe line starts with a tab.
Quick Quiz
1. What must the recipe lines of a Makefile rule start with?
make requires a tab before each recipe line; spaces cause "missing separator".
2. When does make rebuild bill.o in the rule bill.o: bill.c bill.h?
make compares modification times and rebuilds only out-of-date targets.
3. In a rule, what does $< mean?
$@ is the target, $< the first prerequisite and $^ all prerequisites.
4. Why mark clean as .PHONY?
A phony target is always treated as out of date, because it is not a real file.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
