Makefile is a compilation control file widely used for automating project builds. It defines a series of rules to guide the build process. With Makefile, developers can easily manage compilation, linking, cleaning, and other tasks for large projects. This article will start with the basic usage of Makefile and gradually delve into more advanced applications, presenting a comprehensive and detailed manual for using Makefile.
Basic Structure of Makefile
A simple Makefile consists of rules, which are composed of a target, dependencies, and commands:
target: dependencies
commands
The tab character before the command is mandatory. Here is a simple example:
hello: hello.c
gcc -o hello hello.c
Using Variables
Declaring variables in Makefile can make our code more concise.
CC=gcc
CFLAGS=-std=c99
LDFLAGS=
OBJ=main.o utils.o
app: $(OBJ)
$(CC) -o app $(OBJ) $(LDFLAGS)
main.o: main.c
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c utils.h
$(CC) $(CFLAGS) -c utils.c
Generic Rules and Pattern Matching
Pattern rules can reduce the workload of repeating the same commands.
%.o: %.c
$(CC) $(CFLAGS) -c $<
$ is an automatic variable that represents the first item in the dependency list.
Automatic Variables
Makefile provides a series of automatic variables that are very useful in the commands of rules:
-
$@ represents the target file name in the rule; -
$^ represents the list of all dependency files; -
$ represents the first dependency file; -
$? represents the list of all dependencies that are newer than the target.
Using Functions
Many built-in functions in Makefile are available for string operations, file operations, etc.
For example, to get the list of source files:
SRC=$(wildcard *.c)
OBJ=$(patsubst %.c,%.o,$(SRC))
Controlling Make’s Behavior
-
make -B forces recompilation of all targets; -
make -n shows the commands that will be executed without actually executing them; -
make -f <file> specifies a Makefile with a different name; -
make -j allows parallel execution (multi-core compilation).
Advanced Usage – Conditional Statements
Makefile also supports conditional statements, which are very useful when different commands need to be executed in different environments.
ifeq ($(OS),Windows_NT)
RM=del /Q
else
RM=rm -f
endif
clean:
$(RM) *.o
Using Variables and File Includes to Organize Makefile
For large projects, organizing multiple Makefiles is a good approach.
# In the sub-Makefile
include config.mk
Custom Functions
By defining reusable functions, you can make your Makefile more powerful and flexible.
define run-cc
$(CC) $(CFLAGS) -o $@ $^
endef
app: $(OBJ)
$(call run-cc)
Handling Multiple Targets
Define a rule to batch process multiple files.
FILES := file1 file2 file3
all: $(FILES)
$(FILES):
touch $@
Using Phony Targets
Phony targets do not represent actual files; they are simply names for actions.
.PHONY: clean
clean:
rm -f *.o app
Debugging Makefile
You can use make –debug or add comments to help debug Makefile.
app: main.o utils.o
# This is a linking command
$(CC) -o app main.o utils.o
Conclusion
Makefile is a powerful tool for build automation, capable of simplifying the build process for small projects while flexibly managing the complex build systems of large applications. Through this detailed discussion and rich examples, you should be able to grasp the skills of Makefile and apply them in practical projects.
I hope the above content helps you deepen your understanding and use of Makefile. Remember, “Practice makes perfect”—writing your own Makefile and trying out these features is the best way to learn.

END
Source: One Bite Linux
→ Follow for More Updates ←