Public awareness of information security, enlightenment of information security knowledge.
Add WeChat group reply to public account: WeChat group; QQ group: 16004488
Joining the WeChat group or QQ group can get: Learning Tutorial
When compiling a large project, there are often many target files, library files, header files, and the final executable file. There are dependencies between different files. For example, when we compile with the following command:
$gcc -c -o test.o test.c
$gcc -o helloworld test.o
The executable file helloworld depends on test.o for compilation, while test.o depends on test.c.
Dependency
When compiling a large project, we often need to call the compiler many times to compile the entire project step by step based on the dependencies. This method is bottom-up, meaning we compile downstream files first, then upstream files.
The make tool in UNIX systems is used to automatically record and handle dependencies between files. We do not need to input a large number of “gcc” commands; we can complete the entire compilation process by simply calling make. All dependencies are recorded in a makefile text file. We only need to run make helloworld, and make will find all the dependencies needed to compile this file from top to bottom, and finally compile from bottom to top.
(There are multiple versions of make; this article will be based on GNU make. Make will automatically search for makefile, Makefile, or GNUmakefile in the current directory.)
Dependencies
Basic Concepts
We use an example C language file:
#include <stdio.h>
/*
* By Vamei
* test.c for makefile demo
*/
int main()
{
printf(“Hello world!\n”);
return 0;
}
Here is a simple makefile
# helloworld is a binary file
helloworld: test.o
echo “good”
gcc –o helloworld test.o
test.o: test.c
gcc –c –o test.o test.c
Observe the above makefile
-
Lines starting with # are comment lines.
-
target: prerequisite indicates the dependency relationship, i.e., the target file (target) depends on the prerequisite file (prerequisite). There can be multiple prerequisite files, separated by spaces.
-
The indented lines after the dependency relationship are the operations performed to implement the dependency relationship, i.e., normal UNIX commands. A dependency can have multiple operations.
In simple terms:
-
If you want helloworld, you must have test.o and execute the attached operations.
-
If test.o does not exist, you must search for other dependencies and create test.o.
We execute
$make helloworld
to create helloworld.
Make is a recursive creation process:
-
Base Case 1: If the current dependency does not specify prerequisite files, then execute the operation directly.
-
Base Case 2: If the current dependency specifies the target file, and the prerequisite file required by the target file already exists and has not changed since the last make (judged by the most recent write time), also execute the operation of this dependency directly.
-
If the prerequisite file required by the current target file does not exist, or the prerequisite file has changed, then use the prerequisite file as a new target file, find dependencies, and create the target file.
Dashed line: Dependency retrieval
The above is the core function of make. With the above functionality, we can record all dependencies and related operations in the project and use make for compilation. The following content is an extension of this core content.
Macros
Macros (MACRO) can be used in make. Macros are similar to text-type variables. For example, the following CC:
CC = gcc
# helloworld is a binary file
helloworld: test.o
echo “good”
$(CC) –o helloworld test.o
test.o: test.c
$(CC) –c –o test.o test.c
We use CC to represent “gcc”. In the makefile, we use $(CC) to call the value of the macro. Make will replace $(CC) with the value of the macro (gcc) at runtime.
Shell environment variables can be directly called as macros. If a custom macro has the same name as an environment variable, make will prioritize using the custom macro.
(You can use $make -e helloworld to prioritize using environment variables)
Similar to macros in C language, macros in makefile can conveniently manage some fixed texts that appear and facilitate replacement operations. For example, when we use the ifort compiler in the future, we only need to change the macro definition to:
CC = ifort
and it will work.
Internal Macros
Make has internally defined macros that can be used directly. $@ contains the target file name of the current dependency, while $^ contains the prerequisite files for the current target:
CC = gcc
# helloworld is a binary file
helloworld: test.o
echo $@
$(CC) –o $@ $^
test.o: test.c
$(CC) –c –o $@ $^
Internal macros Function
$* Current target file name in the dependency, excluding the suffix.
$* Current prerequisite file in the dependency that has changed
$$ Character “$”
If the target or prerequisite file is a complete path, we can append D and F to extract the folder and file name parts, such as $(@F) indicating the file name part of the target file.
Suffix Dependencies
In the makefile, use
.SUFFIXES: .c .o
to indicate that .c and .o are suffixes.
We can use suffix dependencies, for example:
CC = gcc
.SUFFIXES: .c .o
.c.o:
$(CC) –c –o $@ $^
#————————–
# helloworld is a binary file
helloworld: test.o
echo $@
$(CC) –o $@ $^
test.o: test.c
We define .c and .o as suffixes. We have a suffix dependency relationship .c.o: where the former is the prerequisite and the latter is the target. (Note that this order is different from general dependency relationships)
The above test.o and test.c have a dependency relationship but no operations. Make will find that this dependency relationship meets the .c.o suffix dependency and execute the operations following that suffix dependency.
Suffix dependencies are very useful when the project is large. Files that meet suffix dependencies often have similar operations, and we can represent these operations with suffix dependencies to avoid repetitive input.
Others
The continuation character in the makefile is
In makefile, the following dependency relationships are often defined:
all:
If make is not followed by a file name, then this dependency will be executed.
clean:
Commonly used to clean up historical files.
For example:
CC = gcc
.SUFFIXES: .c .o
.c.o:
$(CC) –c –o $@ $^
#————————–
all: helloworld
<a href=“http://www.jobbole.com/members/echo”>@echo</a> “ALL”
# helloworld is a binary file
helloworld: test.o
<a href=“http://www.jobbole.com/members/echo”>@echo</a> $@
$(CC) –o $@ $^
test.o: test.c
clean:
–rm helloworld *.o
Note: The command after echo will not display the command itself. The command after – will ignore errors (e.g., deleting non-existent files).
Summary
The core function of make is to manage compilation based on dependencies.
The other function of make is to allow users to write makefiles more conveniently.
▼ Click Read Original to see more exciting articles.