Introduction
A Makefile is a build control file commonly used for automated project builds. It defines a set of rules that guide the build process.
Using a Makefile, developers can manage compilation, linking, and clean tasks for large projects.
This article covers basic Makefile usage and progresses to more advanced topics, providing a comprehensive reference.
Basic Structure of a Makefile
The simplest Makefile contains rules. A rule consists of a target, dependencies, and commands:
target: dependencies
commands
The Tab character before commands is required. Example:
hello: hello.c
gcc -o hello hello.c
Using Variables
Declaring variables in a Makefile helps keep it concise.
CC=gcc
CFLAGS=-std=c99
LDFLAGS=
OBJ=main.o utils.o
app: $(OBJ)
[TAB]$(CC) -o app $(OBJ) $(LDFLAGS)
main.o: main.c
[TAB]$(CC) $(CFLAGS) -c main.c
utils.o: utils.c utils.h
[TAB]$(CC) $(CFLAGS) -c utils.c
Pattern Rules
Pattern rules reduce repetition of identical commands.
%.o: %.c
[TAB]$(CC) $(CFLAGS) -c $<
$< is one of the automatic variables and represents the first dependency.
Automatic Variables
Makefile provides several automatic variables useful inside rule commands:
- $@ indicates the target filename;
- $^ indicates the list of all dependencies;
- $< indicates the first dependency;
- $? indicates the list of dependencies that are newer than the target.
Built-in Functions
Makefile includes many functions for string and file operations.
For example, to get a list of source files:
SRC=$(wildcard *.c)
OBJ=$(patsubst %.c,%.o,$(SRC))
Controlling Make Behavior
- make -B forces rebuilding all targets;
- make -n shows the commands that would be executed without running them;
- make -f specifies an alternate Makefile filename;
- make -j enables parallel execution (multi-core builds).
Advanced Use - Conditional Statements
Makefile supports conditional evaluation, useful when different environments require different commands.
ifeq ($(OS),Windows_NT)
[TAB]RM=del /Q
else
[TAB]RM=rm -f
endif
clean:
[TAB]$(RM) *.o
Organizing Makefiles with Variables and Includes
For large projects, splitting configuration into multiple Makefiles is a common practice.
# In a subordinate Makefile
include config.mk
Custom Functions
Defining reusable functions makes a Makefile more powerful and flexible.
define run-cc
$(CC) $(CFLAGS) -o $@ $^
endef
app: $(OBJ)
[TAB]$(call run-cc)
Handling Multiple Targets
Define rules to handle multiple files in bulk.
FILES := file1 file2 file3
all: $(FILES)
$(FILES):
[TAB]touch $@
Phony Targets
Phony targets do not represent real files; they are labels for actions.
.PHONY: clean
clean:
[TAB]rm -f *.o app
Debugging Makefiles
Use make --debug or add comments to assist in debugging a Makefile.
app: main.o utils.o
[TAB]# This is a link command
[TAB]$(CC) -o app main.o utils.o
Conclusion
Makefile is a powerful tool for build automation, suitable for both small projects and complex build systems. The examples and explanations here should enable practical usage of common Makefile features. Writing and testing your own Makefiles is the most effective way to learn.