This article is published by reader Liang Xu from the public account, please do not reprint without authorization!
1. What is Makefile
A typical enterprise-level project usually has many source files, sometimes categorized by function, type, or module in different directories, and sometimes multiple program source codes are stored in one directory.
In this case, how to compile these codes becomes a problem. Makefile is born for this problem; it defines a set of rules that determine which files to compile first, which files to compile later, and which files need to be recompiled.
The entire project can usually be compiled and linked, even with more complex functions, with just one make command. It can be said that any Linux source program comes with a Makefile.
2. Advantages of Makefile
-
Manage code compilation, decide which files to compile, the order of compilation, and whether recompilation is necessary;
-
Save compilation time. If a file has changed, only this file needs to be recompiled, without recompiling the entire project;
-
A one-time effort. Makefile usually only needs to be written once, and later modifications are minimal.
3. Naming Rules
In general, Makefile can be named Makefile or makefile, but many source files are named in lowercase, so more programmers adopt the name Makefile to have it displayed first.
If you name the Makefile something else, like Makefile_demo, it is allowed, but you should use it as follows:
make -f Makefile_demo
4. Basic Rules
The basic rule of Makefile is:
Target: Dependencies
(tab) rule
Target –> The target file to be generated
Dependencies –> Files needed to generate the target
Rule –> The means to generate the target file from the dependency files
tab –> Each rule must start with a tab; spaces will not work
For example, what we often write as gcc test.c -o test can be written as:
test: test.c
gcc test.c -o test
Here, the first line’s test is the target to be generated, and test.c is the dependency; the second line is the rule for generating test from test.c.
Sometimes there are multiple targets in Makefile, but Makefile will set the first target as the ultimate target.
5. Working Principle
Generating Targets:
a. Check if the dependency files in the rules exist;
b. If the dependency files do not exist, look for rules to generate that dependency file.
For example, in the image above, the rule to generate calculator is gcc main.o add.o sub.o mul.o div.o -o, Makefile will first check if main.o, add.o, sub.o, mul.o, div.o exist; if they do not exist, it will look for rules that can generate that dependency file.
If main.o is missing, Makefile will look for rules to generate main.o. When it finds gcc main.c -o main.o can generate main.o, it will use this rule to generate main.o and then generate the ultimate target calculator.
The entire process is downward dependency search, then upward command execution, generating the ultimate target.
Updating Targets:
a. Check all dependencies of the target; if any dependency is updated, regenerate the target;
b. If the target file is newer than the dependency file, it needs to be updated.
For instance, if main.c is modified, the main.o target will be recompiled, and when main.o is updated, the ultimate target calculator will also be recompiled. The same applies to updates of other files.
6. Command Execution
make:
Use this command to generate target files according to predefined rules.
If the name of the Makefile file is not Makefile or makefile, the -f option should be added, for example:
make -f Makefile_demo
make clean:
Clears intermediate files (.o files) and final target files generated during compilation.
If there is a file named clean in the current directory, this command will not execute.
–> Solution: Declare a phony target: .PHONY:clean.
Special Symbols:
– : Indicates that this command will continue executing subsequent commands even if an error occurs. For example:
-rm a.o build/
@: Indicates that this command will execute without echoing. Generally, when executing rules, the terminal prints the executing rules, but with this symbol, only the command will execute without echoing the executing rules. For example:
@echo $(SOURCE)
7. Ordinary Variables
Variable Definition and Assignment:
Variables can be defined directly using assignment, such as:
INCLUDE = ./include/
Variable Value Retrieval:
Enclose it in parentheses and add a dollar sign, such as:
FOO = $(OBJ)
System-provided variables:
Usually in uppercase, such as CC, PWD, CFLAG, etc.
Some have default values, some do not. For example, several common ones:
CPPFLAGS : Options needed by the preprocessor, such as: -I
CFLAGS: Parameters used during compilation –Wall –g -c
LDFLAGS : Options for linking libraries –L -l
The default values of variables can be modified; for example, the default value of CC is cc, but it can be changed to gcc: CC=gcc
8. Automatic Variables
Common Automatic Variables:
Makefile provides many automatic variables, but the following three are commonly used. These automatic variables can only be used in commands within rules and not elsewhere.
$@ –> The target in the rule
$< –> The first dependency condition in the rule
$^ –> All dependency conditions in the rule
For example:
app: main.c func1.c func2.c
gcc $^ – o $@
Here: $^ represents main.c func1.c func2.c, $< represents main.c, and $@ represents app.
Pattern Rules:
Pattern rules use % in targets and dependencies to match corresponding files. For example, if there are main.c, func1.c, and func2.c in the directory, the compilation of these three files can be done with one rule:
%.o:%.c
$(CC) -c $< -o $@
This pattern rule indicates:
main.o is generated from main.c,
func1.o is generated from func1.c,
func2.o is generated from func2.c
This is the function of pattern rules, which can match all files in the directory at once.
9. Functions
Makefile also provides us with a lot of functions, and the following two are frequently used. It should be noted that all functions in Makefile must have return values. In the following examples, suppose there are main.c, func1.c, and func2.c in the directory.
wildcard:
Used to find specified types of files in the specified directory, with the parameters being the directory + file type, for example:
src = $(wildcard ./src/*.c)
This line means: find all files with a .c suffix in the ./src directory and assign them to the variable src.
After the command execution, the value of src will be: main.c func1.c func2.c.
patsubst:
Match and replace. For example, in the following example, it is used to find all files ending with .c in the src directory and replace them with .o files, assigning the result to obj.
obj = $(patsubst %.c ,%.o ,$(src))
This replaces all files with a .c suffix in the src variable with .o.
After the command execution, the value of obj will be main.o func1.o func2.o
Specifically, if you want to place all .o files in the obj directory, you can use the following method:
ob = $(patsubst ./src/%.c, ./obj/%.o, $(src))
10. Summary
Makefile actually provides a lot of functionalities, but what is written in this article is sufficient for general enterprise applications. Especially for beginners, learning some basic knowledge (like this article) and then supplementing it with some cases (like several cases in this series) can fully meet enterprise standards. As the saying goes, to grasp the main contradictions of things, one can first thoroughly understand the basic knowledge before extending to other knowledge of Makefile.
Follow the author’s public account to grow together!
Kind Reminder:
I have created a technical exchange group with experts from various industries, where everyone can chat about technical content and article recommendations; if you want to join, add me on WeChat, note “Technical Group“, those who do not note will not be accepted!
Finally, follow my public account; the book giveaway in July is coming soon:
