【Linux系统】自动化构建-make/Makefile的使用
文章目录
- 一、make/Makefile 的简单介绍
- 二、make/Makefile的基本使用
- 1.示例展现基本使用
- 2.依赖关系和依赖方法
- 3.伪目标(.PHONY 修饰)
- 三、make自动推导依赖关系
- 四、使用make/Makefile对多个.c文件进行自动化编译(语法补充)
- 1.定义变量
- 2.隐藏执行过程
- 3.模拟实现对多个.c文件的自动化编译
一、make/Makefile 的简单介绍
make是一条命令(Linux系统内置),Makefile是一个文件(工程师自己创建),两个搭配使用,完成项目自动化构建。
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- make是一个命令工具,是一个解释Makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
- Makefile带来的好处就——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
二、make/Makefile的基本使用
1.示例展现基本使用
要使用make指令,首先得创建一个Makefile文件,并在其中编辑一些操作:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ls -l
total 4
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ vim code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ touch Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ vim Makefile
make指令能够执行Makefile文件中的操作:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ls -l
total 8
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 65 May 11 16:35 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ls -l
total 20
-rwxrwxr-x 1 zh zh 8360 May 11 16:52 code
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 65 May 11 16:35 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ./code
hello world
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make clean
rm -f code
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ls -l
total 8
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 65 May 11 16:35 Makefile
2.依赖关系和依赖方法
这个Makefile文件中,
依赖关系是 code:code.c。表示文件code,它依赖于code.c (而当前目录下存在code.c,可以执行依赖方法,不存在code.c会报错)
依赖方法是 gcc -o code code.c (实现依赖关系的方法)
make执行Makefile文件中的操作是自上而下的,所以直接使用make指令会默认执行Makefile文件中的第一个操作。
像clean这种,没有被第⼀个目标⽂件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示使用make执行,即命令——“make clean”,以此来清除所有的目标文件,以便重新编译。
3.伪目标(.PHONY 修饰)
⼀般这种clean操作,我们会将它设置为伪目标,用 .PHONY 修饰后就是伪目标,伪目标的特性是:总是被执行的。
- 要想理解伪目标总是被执行的特性,我们首先要感受一下不加 .PHONY 修饰的效果。这个Makefile文件中的第一个操作就是不加.PHONY 修饰,多次运行它展示一下效果:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 65 May 11 18:51 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 20
-rwxrwxr-x 1 zh zh 8360 May 11 18:54 code
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 65 May 11 18:51 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
只有第一次使用make指令时执行了Makefile文件中的第一个操作。
- 对比试验,将Makefile文件中的第一个操作设置为伪目标,再多次运行它展示一下效果:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 77 May 11 19:07 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 20
-rwxrwxr-x 1 zh zh 8360 May 11 19:07 code
-rw-rw-r-- 1 zh zh 75 May 10 16:35 code.c
-rw-rw-r-- 1 zh zh 77 May 11 19:07 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
发现每次使用make指令时都执行了Makefile文件中的第一个操作。通过对比试验确实能感受到伪目标总是被执行的特性
原理解析(解释Makefile文件中的第一个操作不加.PHONY 修饰时,为什么总是不被执行):
这实际上与文件的Modify时间有关。当第一次使用make指令时执行了Makefile文件中的第一个操作时,创建了code文件,code是后创建的,它的 Modify时间 是: 2025-05-11 19:35:10,比code.c文件的Modify时间新。
只有code.c文件的Modify时间比code文件的新时,后续使用make指令才会执行了Makefile文件中的第一个操作:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 102 May 11 19:26 code.c
-rw-rw-r-- 1 zh zh 65 May 11 19:25 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 20
-rwxrwxr-x 1 zh zh 8360 May 11 19:35 code
-rw-rw-r-- 1 zh zh 102 May 11 19:26 code.c
-rw-rw-r-- 1 zh zh 65 May 11 19:25 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ stat code.cFile: ‘code.c’Size: 102 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 927189 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ zh) Gid: ( 1000/ zh)
Access: 2025-05-11 19:26:46.355112598 +0800
Modify: 2025-05-11 19:26:46.354112560 +0800
Change: 2025-05-11 19:26:46.354112560 +0800Birth: -
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ stat codeFile: ‘code’Size: 8360 Blocks: 24 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 924027 Links: 1
Access: (0775/-rwxrwxr-x) Uid: ( 1000/ zh) Gid: ( 1000/ zh)
Access: 2025-05-11 19:35:10.459030471 +0800
Modify: 2025-05-11 19:35:10.459030471 +0800
Change: 2025-05-11 19:35:10.459030471 +0800Birth: -
我们修改一下 code.c 文件的Modify时间来验证一下上述结论,Modify时间是文件内容的最近修改时间,所以我们修改一下 code.c 文件中的内容就可以更新它的Modify时间。当更新了 code.c 文件的Modify时间后,使用make指令又能执行Makefile文件中的第一个操作,但只有更新之后第一次使用make命令有效,因为执行gcc -o code code.c 操作后,编译形成了新的 code 文件,它的Modify时间又比 code.c 文件的新了。
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ vim code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ stat code.cFile: ‘code.c’Size: 75 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 927189 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ zh) Gid: ( 1000/ zh)
Access: 2025-05-11 19:44:53.539912417 +0800
Modify: 2025-05-11 19:44:53.538912380 +0800
Change: 2025-05-11 19:44:53.538912380 +0800Birth: -
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -o code code.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
make: `code' is up to date.
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ stat codeFile: ‘code’Size: 8360 Blocks: 24 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 924027 Links: 1
Access: (0775/-rwxrwxr-x) Uid: ( 1000/ zh) Gid: ( 1000/ zh)
Access: 2025-05-11 19:45:23.016018601 +0800
Modify: 2025-05-11 19:45:23.016018601 +0800
Change: 2025-05-11 19:45:23.016018601 +0800Birth: -
结论: Makefile文件中对于文件的编译操作不需要被设置为伪目标,因为当文件内容更改,Modify时间更新,文件才被重新编译,这样非常合理,能够使编译工作变得更加高效;
而clean等操作可以被设置成伪目标,因为清理等工作很重要,而且不像编译操作那么费时,每次使用时都应该被执行。
三、make自动推导依赖关系
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 75 May 11 19:44 code.c
-rw-rw-r-- 1 zh zh 204 May 11 20:38 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -E code.c -o code.i
gcc -S code.i -o code.s
gcc -c code.s -o code.o
gcc -o code code.o
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 48
-rwxrwxr-x 1 zh zh 8360 May 11 20:39 code
-rw-rw-r-- 1 zh zh 75 May 11 19:44 code.c
-rw-rw-r-- 1 zh zh 16872 May 11 20:39 code.i
-rw-rw-r-- 1 zh zh 1496 May 11 20:39 code.o
-rw-rw-r-- 1 zh zh 447 May 11 20:39 code.s
-rw-rw-r-- 1 zh zh 204 May 11 20:38 Makefile
make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件,在上面的例子中,他会找到 code 这个文件,并把这个文件作为最终的目标文件。
- 如果 code 文件不存在,或是 code 所依赖的后面的 code.o 文件的Modify时间要比 code 这个文件新,那么,他就会执行后面所定义的命令来生成 code 文件。
- 如果 code 所依赖的 code.o 文件不存在,那么 make 会在Makefile文件中继续向下找目标为 code.o 文件的依赖性,如果找到则再根据第3步的规则生成 code.o 文件。(如果code.o所依赖的 code.s 文件不存在,则进行与第4步同样的操作,这有点像一个堆栈的过程)
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
四、使用make/Makefile对多个.c文件进行自动化编译(语法补充)
1.定义变量
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 75 May 11 19:44 code.c
-rw-rw-r-- 1 zh zh 259 May 11 22:54 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -c code.c
gcc -o code code.o
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 24
-rwxrwxr-x 1 zh zh 8360 May 11 22:54 code
-rw-rw-r-- 1 zh zh 75 May 11 19:44 code.c
-rw-rw-r-- 1 zh zh 1496 May 11 22:54 code.o
-rw-rw-r-- 1 zh zh 259 May 11 22:54 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ./code
hello world
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make clean
rm -f code.o code
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 16
-rw-rw-r-- 1 zh zh 75 May 11 19:44 code.c
-rw-rw-r-- 1 zh zh 259 May 11 22:54 Makefile
对Makefile文件进行进一步改进,可以用 $@ 指代目标文件, $^ 指代依赖文件:
2.隐藏执行过程
在依赖方法前+@,当执行该条依赖方法时,会隐藏执行过程:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 338 May 11 23:08 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 24
-rwxrwxr-x 1 zh zh 8360 May 11 23:10 code
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 1496 May 11 23:10 code.o
-rw-rw-r-- 1 zh zh 338 May 11 23:08 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make clean
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 338 May 11 23:08 Makefile
3.模拟实现对多个.c文件的自动化编译
- 首先得先创建多个.c文件:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 248 May 11 23:35 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ touch code{1..5}.c
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 8
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code1.c
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code2.c
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code3.c
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code4.c
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code5.c
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 248 May 11 23:35 Makefile
- 编辑Makefile文件:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ vim Makefile
%.c 的作用是展开当前目录下所有的.c文件
%.o 的作用是展开所有.c文件的同名.o
$< 的作用是对展开的依赖.c文件,一个一个的交给gcc进行处理
- 使用make指令调用Makefile文件对多个.c文件进行自动化编译:
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ make
gcc -c code3.c
gcc -c code5.c
gcc -c code.c
gcc -c code4.c
gcc -c code1.c
gcc -c code2.c
gcc -o code code3.o code5.o code.o code4.o code1.o code2.o
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ll
total 44
-rwxrwxr-x 1 zh zh 8520 May 12 16:20 code
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code1.c
-rw-rw-r-- 1 zh zh 936 May 12 16:20 code1.o
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code2.c
-rw-rw-r-- 1 zh zh 936 May 12 16:20 code2.o
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code3.c
-rw-rw-r-- 1 zh zh 936 May 12 16:20 code3.o
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code4.c
-rw-rw-r-- 1 zh zh 936 May 12 16:20 code4.o
-rw-rw-r-- 1 zh zh 0 May 12 16:13 code5.c
-rw-rw-r-- 1 zh zh 936 May 12 16:20 code5.o
-rw-rw-r-- 1 zh zh 79 May 11 23:10 code.c
-rw-rw-r-- 1 zh zh 1496 May 12 16:20 code.o
-rw-rw-r-- 1 zh zh 407 May 12 16:20 Makefile
[zh@iZbp1dr1jtgcuih41mw88oZ ~]$ ./code
hello world!