MAKEFILE
前言
makefile 是关系整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
makefile 带来的好处:
- 自动化编译,一个make命令就可以使整个工程自动编译。
提示:后续系列参考陈皓老师教程,如有侵权,请联系删除。
一、Makefile基础介绍
make命令执行时,需要一个makefile文件,以告诉make命令需要依照怎样的规则去编译和链接程序。
首先,我们用一个示例来说明makefile的书写规则,以便给大家一个感性认识。这个示例来源于gnu 的make使用手册,在这个示例工程中有8个c文件,和3个头文件,之后编写一个makefile来告诉make命令如何编译和链接这几个文件。规则如下:
- 如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
- 如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。
只要我们的makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自动编译所需要的文件和链接目标程序。
1.1 Makefile的基本规则
一个简单的(示例):
target ... : prerequisites ...
command
...
...
target:
- 可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
prerequisites:
- 生成该target所依赖的文件和/或target
command:
- 该target要执行的命令(任意的shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。简单表示就是:
目标文件:依赖文件
shell 指令 1
shell 指令 2
shell 指令 3
... ...
shell 指令 n
目标文件根据n条shell指令操作依赖文件,最终生成目标文件。
关于程序编译的一些规范和方法
一般来说,无论是 C 还是 C++,首先要把源文件编译成中间代码文件,在 Windows 下也就是 .obj 文件,UNIX 下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的 Object File 合成执行文件,这个动作叫作链接(link)。
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在 C/C++ 文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(.o 文件或 .obj 文件)。
链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件(.o 文件或 .obj 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在 Windows 下这种包叫“库文件”(Library File),也就是 .lib 文件,在 UNIX 下,是 Archive File,也就是 .a 文件。
1.2 Makefile示例
规则如下(示例):
如果一个工程有3个头文件和8个c文件,按照1中所述的那三个规则,对应的makefile可以写成下面的这个样子。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
-
反斜杠
( \ )
是换行符的意思。便于makefile的阅读。我们可以把这个内容保存在名为“makefile”或“Makefile”的文件中,然后在该目录下直接输入命令make
就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,执行make clean
就可以。 -
在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件
( *.o )
,依赖文件(prerequisites)就是冒号后面的那些.c
文件和.h
文件。每一个.o
文件都有一组依赖文件,而这些.o
文件又是执行文件edit
的依赖文件。 -
依赖关系的实质就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
-
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个 Tab 键作为开头。
-
make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
-
这里
clean
不是一个文件,它只不过是一个动作名字,有点像c语言中的label一样(对应于第一节的标签),其冒号后什么也没有,那么,make
就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个label的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
该处使用的url网络请求的数据
1.3 Makefile工作原理
以第一章第2节中的示例为例,在默认的方式下,也就是我们只输入 make
命令。make的执行逻辑如下:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“
edit
”这个文件,并把这个文件作为最终的目标文件。 - 如果edit文件不存在,或是
edit
所依赖的后面的.o
文件的文件修改时间要比edit
这个文件新,那么,他就会执行后面所定义的命令来生成edit
这个文件。 - 如果
edit
所依赖的.o
文件也不存在,那么make会在当前文件中找目标为.o
文件的依赖性,如果找到则再根据那一个规则生成.o
文件。(类似于递归的过程) - 如果
C文件和H文件
是存在的啦,于是make会生成 .o
文件,然后再用.o
文件生成make的可执行文件 edit 。
make的依赖性:
-
make会逐层去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在找了依赖关系之后,冒号后面的文件还是不在,那么就不再进行下一步工作。
-
像
clean
这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以用显示命令执行——make clean
,以此来清除所有的目标文件,以便重编译。
1.4 Makefile中使用变量
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
- 上面的例子中,我们可以看到
.o
文件的字符串被重复了两次(一次是依赖条件,一次是编译规则),如果我们的工程需要加入一个新的 .o 文件,那么在这两个地方都需要添加。当然,示例中的makefile并不复杂,所以在两个地方加也不麻烦,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为提高makefile的维护性,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,相当于C语言中的宏定义。 - 比如,我们声明一个变量,叫
objects , OBJECTS , objs , OBJS , obj
或是OBJ
,反正不管什么啦,只要能够表示obj文件就行了。那么在makefile一开始就这样定义:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是,我们可以很方便地在makefile中以 $(objects) 的方式来使用这个变量,于是改良版makefile变成下面这个样子:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects) //这里用变量代替
cc -o edit $(objects) //这里用变量代替
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects) //这里用变量代替
所以如果有新的 .o
文件需要添加,我们只需简单地修改一下 objects
变量即可。
- 这里简单补充一点Linux关于
$()
、${}
的介绍:
- 在bash中,
$( )
与' '
(反引号)都是用来作命令替换的。在操作上,这两者都是达到相应的效果,但是建议使用$( ),更加直观。
[root@localhost ~]# echo today is $(date "+%Y-%m-%d")
today is 2020-12-17
[root@localhost ~]# echo today is `date "+%Y-%m-%d"`
today is 2020-12-17
- 一般情况下,
$ var
与$ {var}
是没有区别的,主要用于变量替换,但是用${ }会比较精确的界定变量名称的范围 。
[root@localhost ~]# A=SUSE
[root@localhost ~]# echo $AB #表示变量AB
[root@localhost ~]# echo ${A}B #表示变量A后连接着B
SUSEB
1.5 自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们无须在每一个 .o
文件后都写上类似的命令,因为,make会自动识别,并自己推导命令中的隐含关系。
只要make找到一个 .o
文件,它就会自动的把 .c
文件加在依赖关系中,如果make找到一个 files.o
,那么 files.c
就会是 files.o
的依赖文件。并且 cc -c files.c
也会被推导出来,所以更加简单的makefile可以写成如下:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean # .PHONY:伪目标文件
clean :
rm edit $(objects) # 指令一定以TAB开头
- 这种方法是make的“隐晦(含)规则”。上面makefile内容中,
.PHONY
表示clean
是个伪目标文件。
1.6 清空目标文件的规则
- 每个Makefile中都应该写一个清空目标文件(
.o 和执行文件
)的规则,这不仅便于重编译,也很利于保持文件的整理。一般的风格如下:
clean:
rm edit $(objects)
更为稳健的做法是(可依据实际情况选择):
.PHONY : clean # .PHONY 表示 clean 是一个“伪目标”
clean :
-rm edit $(objects)
rm
命令前面加了一个-
小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事,即忽略可能出现的问题继续执行。clean
的规则不要放在文件的开头,不然,这就会变成make的默认目标。约定俗成的是——“clean从来都是放在文件的最后”。
总结
本章仅简单介绍了Makefile的基本规则,后续未完待续。