看懂Makefile
什么是Makefile
makefile
是一种说明编译规则的文件,带来的好处就是自动化编译,极大提高开发的效率
关于编译和链接
无论是C耗时C++都需要把源文件编译成中间件,这个过程叫做编译
然后将这些大量的中间文件链接合成一个可执行文件,这个过程叫做链接
Makefile文件命名和规则
- 文件命名
文件命名必须是
Makefile
或是makefile
只能是这两种
- Makefile规则: 文件中可以有一个或多个规则
目标 ... : 依赖 ...
命令(shell命令)
...
...
...
-
目标: 最终要生成的文件(伪目标除外),可以使.o .i .s文件,也可以是可执行文件
-
依赖: 生成目标锁依赖的文件或者目标
-
命令: 通过执行命令对依赖进行操作生成目标
Makefile中的掐规则一般都是为第一条规则服务的
\
是换行符方便阅读
基本原理
-
再命令执行前会检查依赖是否存在
a. 如果存在,执行命令
b. 如果不存在,向下检查其他规则,检查有没有一个规则用来生成这个依赖的,如果找到了,则执行该规则中的命令
-
检查更新
a.如果依赖的时间比目标时间晚,需要重新生成目标
b.如果依赖的时间比目标早,目标不需要进行更新,对应规则中的命令就不需要执行
变量、模式匹配、函数
Makefile
支持三个通配符==*
?
~
==与shell
是一样的
自动变量
简单使用时目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。自动化变量就是完成这个功能的
$@
:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。$%
:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么," %"就是"bar.o"," @“就是"foo.a”。如果目标不是函数库文件(Unix 下是[.a],Windows 下是[.lib]),那么,其值为空。$<
:依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?
:所有比目标新的依赖目标的集合。以空格分隔。$^
:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。$+
:这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*
:这个变量表示目标模式中"%“及其之前的部分。如果目标是"dir/a.foo.b”,并且目标的模式是"a.%.b",那么," ∗ " 的 值 就 是 " d i r / a . f o o " 。 这 个 变 量 对 于 构 造 有 关 联 的 文 件 名 是 比 较 有 较 。 如 果 目 标 中 没 有 模 式 的 定 义 , 那 么 " *"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么" ∗"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"“也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么” ∗ " 就 是 除 了 后 缀 的 那 一 部 分 。 例 如 : 如 果 目 标 是 " f o o . c " , 因 为 " . c " 是 m a k e 所 能 识 别 的 后 缀 名 , 所 以 , " *"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以," ∗"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"“的值就是"foo”。这个特性是GNUmake的,很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用" ∗ " , 除 非 是 在 隐 含 规 则 或 是 静 态 模 式 中 。 如 果 目 标 中 的 后 缀 是 m a k e 所 不 能 识 别 的 , 那 么 " *",除非是在隐含规则或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么" ∗",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"*"就是空值。$(@D)
:表示 “ @ " 的 目 录 部 分 ( 不 以 斜 杠 作 为 结 尾 ) , 如 果 " @"的 目录 部 分( 不以斜杠作 为结尾 ), 如 果" @"的目录部分(不以斜杠作为结尾),如果"@“值是"dir/foo.o”,那么” ( @ D ) " 就 是 " d i r " , 而 如 果 " (@D)"就是"dir",而如果" (@D)"就是"dir",而如果"@“中没有包含斜杠的话,其值就是”."(当前目录)。$(@F)
:表示" @ " 的 文 件 部 分 , 如 果 " @"的文件部分,如果" @"的文件部分,如果"@“值是"dir/foo.o”,那么" ( @ F ) " 就 是 " f o o . o " , " (@F)"就是"foo.o"," (@F)"就是"foo.o","(@F)“相当于函数”$(notdir $@)"。$(*D)
:和上面所述的同理,也是取文件的目录部分。对于上面的那个例子,"$(*D)“返回"dir”。$(*F)
:和上面所述的同理,也是取文件的文件部分。对于上面的那个例子,"$(*F)“返回"foo”。$(%D)
:表示了函数包文件成员的目录部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。$(%F)
:表示了函数包文件成员的文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。$(<D)
:表示依赖文件的目录部分。$(<F)
:表示依赖文件的文件部分。$(^D)
:表示所有依赖文件的目录部分。(无相同的)$(^F)
:表示所有依赖文件的文件部分。(无相同的)$(+D)
:表示所有依赖文件的目录部分。(可以有相同的)$(+F)
:所有依赖文件的文件部分。(可以有相同的)$(?D)
:表示被更新的依赖文件的目录部分。$(?F)
:表示被更新的依赖文件的文件部分。
最后,对于" < " , 为 了 避 免 产 生 不 必 要 的 麻 烦 , 最 好 给 <",为了避免产生不必要的麻烦,最好给 <",为了避免产生不必要的麻烦,最好给后面的那个特定字符都加上圆括号,比如," ( < ) " 就 要 比 " (< )"就要比" (<)"就要比"<"要好一些。
伪目标
最早先的一个例子中,我们提到过一个“clean”的目标
,这是一个“伪目标”,
clean:
rm *.o temp
正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)
因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
.PHONY : clean
clean :
rm *.o temp
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 .PHONY : all
声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
编译的过程
编译的过程分为
Makefile示例
app:sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app
这里生成app目标, 依赖的是.c文件 命令是gcc命令
app:sub.o add.o mult.o div.o main.o
gcc sub.o add.o mult.o div.o main.o -o app
sub.o:sub.c
gcc -c sub.c -o sub.o
add.o:add.c
gcc -c add.c -o add.o
mult.o:mult.c
gcc -c mult.c -o mult.o
div.o:div.c
gcc -c div.c -o div.o
main.o:main.c
gcc -c main.c -o main.o
# 依赖.o文件没有向下寻找生成.o文件的规则
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
sub.o:sub.c
gcc -c sub.c -o sub.o
add.o:add.c
gcc -c add.c -o add.o
mult.o:mult.c
gcc -c mult.c -o mult.o
div.o:div.c
gcc -c div.c -o div.o
main.o:main.c
gcc -c main.c -o main.o
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
# 使用变量和自动变量