有感于网络上很多技术博客的排版方式不够统一,并趋向于随意,影响学习的效率和动力,故而根据自己的理解将其重新编排如下,不算原创,算作是自己的学习心得吧,也可供自己日后翻看。
一个Makefile例子
make 命令执行时,需要根据一些规则来决定按照怎么样的方式去编译和链接程序,这些规则就由 makefile 文件所指定。如果我们 makefile 文件写的足够好,make 命令会自动地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。
首先,本文将给出一个makefile文件的示例,以便大家能有一个直观感受,这个例子来源于GNU的make使用手册。在这个例子中,我们的工程有8个c文件,和3个头文件,我们要写一个makefile来告诉make命令如何编译和链接这几个文件。例子如下:
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
这个例子里 make 的编码规则如下:
a. 如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
b. 如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
c. 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。
Makefile规则
在详细拆解上一节的 Makefile 之前,先来看下 Makefile 的基本范式。
target ... :prerequisites ...
command
...
target可以是一个 1) object file(可执行文件),2) 可执行文件,还可以是个3) label(标签),关于标签这个特性,在后面的伪目标章节还会有叙述。
prerequisites 就是,要生成那个target所需要的文件或是目标。command 也就是 make 需要执行的命令,可以是任意的
shell 命令。
这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command中。同时,prerequisites 中如果有一个以上的文件比target文件要新的话,command 所定义的命令就会被执行。这就是Makefile的规则,也是 Makefile 中最核心的内容。
有了这些规则后,再来分析上面的例子。在这个 makefile 中,目标文件(target)包含:
- 执行文件
edit
- 中间目标文件(
*.o
)
依赖文件(prerequisites)就是冒号后面的那些 .c
文件和 .h
文件。每一个 .o
文件都有一组依赖文件,而这些 .o
文件又是执行文件 edit 的依赖文件。
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的系统命令,一定要以一个tab键作为开头。make会比较
targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比targets文件的日期要新,或者 target 不存在的话,那么,make就会执行后续定义的命令。
我们可以把这个内容保存在名字为makefile
或Makefile
的文件中,然后在该目录下直接输入命令 make 就可以生成可执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下 make clean
就可以了。注:反斜线(\)是换行符的意思,这样比较便于阅读。
这里要说明一点的是,clean 不是一个文件,它只不过是一个动作名字,有点像C语言中的 lable 一样,其冒号后什么也没有,那么,make就不会去找它的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令(不仅用于 clean,其他 lable 同样适用),就要在 make 命令后显式指出这个 lable 的名字。这样的方法非常有用,我们可以在一个 makefile 中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
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 职责范围内。
通过上述分析,我们知道,像 clean 这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要 make 执行。即命令make clean
,以此来清除所有的目标文件,以便重编译。
在Makefile中使用变量
在上面的例子中可以看到,后缀为.o
的一大串文件名写了两次,这样比较费时费力,而且如果文件有所增减,要修改的地方也非常多,对以后的维护造成困难。在这种情形下,我们可以在Makefile里使用变量代替这一大串依赖文件,这里变量的使用方式基本类似于shell脚本里变量的使用方法。
我们可以在makefile一开始就这样定义:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
那么接下来我们就可以很方便地在我们的Makefile中以$(objects)
的方式来使用这个变量了,于是如果有新的.o
文件加入,我们只需简单地修改一下 objects 变量就可以了。
让 make 自动推导
GNU的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个.o
文件后都写上类似的命令。因为,我们的make会自动识别,并自己推导命令。
只要make看到一个.o
文件,它就会自动的把.c
文件加在依赖关系中,如果make找到一个FILENAME.o
,那么 FILENAME.c
,就会是FILENAME.o
的依赖文件。并且 cc -c FILENAME.c
也会被推导出来,于是,我们的makefile 再也不用写得这么复杂。我们的新makefile就可以这么写了。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc = gcc
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
clean:
rm edit $(objects)
这种方法,也就是make的**。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。