Introduction to Makefile

Introduction to Makefile

Makefile is created for this problem; it defines a set of rules that determine which files need to be compiled first, which files later, and which files need to be recompiled.

— Liang Xu, Linux
Source: zhuanlan.zhihu.com | Author: Liang Xu, Linux

1. What is Makefile

In an enterprise-level project, there are usually many source files, sometimes categorized by functionality, type, or module, and sometimes multiple program source codes are stored in one directory.

In this case, how to compile these codes becomes a problem. Makefile is designed for this problem; it defines a set of rules that determine which files should be compiled first, which files later, and which files need to be recompiled.

The entire project can usually be compiled and linked with just one make command, and even more complex functions can be handled. It can be said that any Linux source program comes with a Makefile file.

2. Advantages of Makefile

1. Manages the compilation of code, deciding which files to compile, the order of compilation, and whether recompilation is needed;
2. Saves compilation time. If a file is changed, only that file needs to be recompiled, without recompiling the entire project;
3. One-time effort. A Makefile usually only needs to be written once, and no further changes are required later.

3. Naming conventions

Generally, you can name the Makefile as Makefile or makefile, but many source files are named in lowercase, so more programmers prefer the name Makefile as it displays it prominently.

If you name the Makefile something else, like Makefile_demo, it is also allowed, but you should use the following method when using it:

make -f Makefile_demo

4. Basic rules

The basic format of a Makefile is:

target: dependencies
	rules
◈ Target –> The target file to be generated
◈ Dependencies –> Some files needed to generate the target
◈ Rules –> The means of generating the target file from the dependency files
tab –> Each rule must start with a tab; spaces will not work

For example, we often write gcc test.c -o test, which 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 can be multiple targets in a Makefile, but the first target is considered the ultimate target.

5. Working principle

Generating targets

1. Check if the dependency files in the rules exist;
2. If the dependency files do not exist, look for rules to generate the dependency file.

Introduction to Makefile

For example, in the above image, the rule to generate calculator is gcc main.o add.o sub.o mul.o div.o -o. The Makefile will first check if main.o, add.o, sub.o, mul.o, and div.o exist. If not, it will look for rules to generate those dependency files.

If, for instance, main.o is missing, the Makefile will look for rules to generate main.o. When it finds the rule 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 involves looking for dependencies downwards, then executing commands upwards to generate the ultimate target.

Updating targets

1. Check all dependencies of the target; if any dependency is updated, regenerate the target;
2. If the target file is newer than the dependency files, it needs to be updated.

Introduction to Makefile

For example, 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 logic applies for updates to other files.

6. Command execution

make: Use this command to generate the target files according to the predefined rules. If the Makefile is not named Makefile or makefile, the -f option should be added, for example:

make -f Makefile_demo

make clean: Clears the intermediate files generated during compilation (the .o files) and the final target files.

If there is a file named clean in the current directory, this command will not execute.

The solution is to declare a phony target: .PHONY: clean.

Special symbols:

-: Indicates that this command will continue executing subsequent commands even if it encounters an error. For example: -rm a.o build/
@: Indicates that the command will execute without echoing. Normally, when executing rules, the terminal prints the executing rules, but adding this symbol will only execute the command without echoing the executing rules. For example: @echo $(SOURCE)

7. Ordinary variables

Defining and assigning variables:

Variables can be defined directly by assignment, like:

INCLUDE = ./include/

Getting variable values:

Wrap in parentheses with a dollar sign, like:

FOO = $(OBJ)

System variables:

Usually in uppercase, such as CC, PWD, CFLAG, etc.

Some have default values, some do not. A few common ones are:

CPPFLAGS: Options needed by the preprocessor, like -I
CFLAGS: Parameters used during compilation, like –Wall –g -c
LDFLAGS: Options used for linking libraries, like –L -l

Default values of variables can be modified, for example, CC defaults to cc, but can be changed to gcc: CC=gcc

8. Automatic variables

Common automatic variables:

The Makefile provides many automatic variables, but the following three are commonly used. These automatic variables can only be used in the commands of the rules, not elsewhere.

$@ –> The target in the rule
$< –> The first dependency in the rule
$^ –> All dependencies 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 the target 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, and 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 many functions, and the following two functions are frequently used. Note that all functions in a Makefile must have a return value. In the following examples, assume there are main.c, func1.c, and func2.c in the directory.

Wildcard:

Used to find specified types of files in a specified directory, the parameters are the directory + file type, for example:

src = $(wildcard ./src/*.c)

This statement means: Find all files with the suffix .c 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, the following example is used to find all files ending with .c in the src directory and replace them with .o files, assigning it to obj.

obj = $(patsubst %.c, %.o, $(src))

After the command execution, the value of obj will be main.o func1.o func2.o.

In particular, if you want to place all .o files in the obj directory, you can use the following method:

obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))

10. Summary

Makefile actually provides a lot of functionality, but what is written in this article is sufficient for general enterprise applications. Especially for beginners, learning some basic knowledge (like this article) along with some examples (like several examples in this series) can fully meet enterprise employment standards. As the saying goes, grasp the main contradictions of things; one can first thoroughly understand the basic knowledge before extending to other knowledge of Makefile.

Introduction to Makefile

Leave a Comment