1、Makefile规则格式:
目标…... : 依赖文件集合……
命令 1
命令 2
……
例如:
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
Make的执行过程:
1、 make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
2、当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
3、当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比
目标文件晚)的话就会执行后面的命令来更新目标。
总结一下, Makefile 中规则用来描述在什么情况下使用什么命令来构建一个特定的文件,
这个文件就是规则的“目标”,为了生成这个“目标”而作为材料的其它文件称为“目标”的依
赖,规则的命令是用来创建或者更新目标的。
除了 Makefile 的“终极目标”所在的规则以外,其它规则的顺序在 Makefile 中是没有意义
的,“终极目标”就是指在使用 make 命令的时候没有指定具体的目标时, make 默认的那个目
标,它是 Makefile 文件中第一个规则的目标,如果 Makefile 中的第一个规则有多个目标,那么
这些目标中的第一个目标就是 make 的“终极目标”。
2、Makefile变量
跟 C 语言一样 Makefile 也支持变量的,先看一下前面的例子:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
上述 Makefile 语句中, main.o input.o 和 calcue.o 这三个依赖文件,我们输入了两遍,我们
这个 Makefile 比较小,如果 Makefile 复杂的时候这种重复输入的工作就会非常费时间,而且非
常容易输错,为了解决这个问题, Makefile 加入了变量支持。不像 C 语言中的变量有 int、 char
等各种类型, Makefile 中的变量都是字符串!类似 C 语言中的宏。使用变量将上面的代码修改,
修改以后如下所示
#Makefile 变量的使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
①赋值符"="
使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,
比如如下代码:
name = yang
currname = $(name)
name = YJL
print:
@echo "name = $(currname)"
执行结果:
可以看到 curname 的值不是“yang”,竟然是“YJL”,也就是变量“name”
最后一次赋值的结果,这就是赋值符“=”的神奇之处!借助另外一个变量,可以将变量的真实
值推到后面去定义。也就是变量的真实值取决于它所引用的变量的最后一次有效值
赋值符“:=”
name = yang
currname := $(name)
name = YJL
print:
@echo "name = $(currname)"
这里只做了一个修改,"currname = $(name)"修改为"currname := $(name)",执行结果如下:
赋值符“?=”
currname = yang
currname ?= YJL
print:
@echo "name = $(currname)"
执行结果:
currname ?= YJL
print:
@echo "name = $(currname)"
执行结果:
“?=”是一个很有用的赋值符,如果变量 currname 前面没有被赋值,那么此变量就是“YJL”,
如果前面已经赋过值了,那么就使用前面赋的值。
④变量追加“+=”
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进
去,此时就要使用到符号“+=”,比如如下所示代码:
objects = main.o inpiut.o
objects += calcu.o
一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因此变
量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。
3、Makefile模式规则
在 3.3.2 小节中我们编写了一个 Makefile 文件用来编译工程,这个 Makefile 的内容如下:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
上述 Makefile 中第 3~8 行是将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对
应的规则,如果工程中 C 文件很多的话显然不能这么做。为此,我们可以使用 Makefile 中的模
式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。
模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”
表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的
文件,类似与通配符, a.%.c 就表示以 a.开头,以.c 结束的所有文件。
举个例子:
把上面的Makefile简化:
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main
第6行:"gcc -c $<"又涉及到另外一个知识点,自动化变量。
4、Makefile自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候
都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生
成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所
定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该
出现在规则的命令中, 常用的自动化变量如表3.4.4.1 :
自动化变量 | 描述 |
$@ | 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模 式中定义的目标集合。 |
$% | 当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件, 那么其值为空。 |
$< | 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%” )定义的,那么 “$<”就是符合模式的一系列的文件集合。 |
$? | 所有比目标新的依赖目标集合,以空格分开。 |
$^ | 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件, “$^”会去除重复的依赖文件,值保留一份。 |
$+ | 和“$^”类似,但是当依赖文件存在重复的话不会去除重复的依赖文件。 |
$* | 这个变量表示目标模式中"%"及其之前的部分,如果目标是 test/a.test.c,目标模 式为 a.%.c,那么“$*”就是 test/a.test。 |
表 3.4.4.1 中的 7 个自动化变量中,常用的三种: $@、 $<和$^
5、Makefile伪目标
Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代
表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命
令。
使用伪目标的主要是为了避免 Makefile 中定义的只执行命令的目标和工作目录下的实际文
件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创
建文件的,比如在前面的“示例代码 3.4.4.1”中有如下代码用来完成清理工程的功能:
clean:
rm *.o
rm main
上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当
我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。可是如果我们“手
贱”,在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时
候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我
们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将 clean 声明为伪
目标,声明方式如下:
.PHONY : clean
我们使用伪目标来更改“示例代码 3.4.4.1”,修改完成以后如下:
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
.PHONY : clean
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main
上述代码第 5 行声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名
为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。
6、Makefile条件判断
在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支, Makefile 也支持
条件判断,语法有两种如下:
<条件关键字>
<条件为真时执行的语句>
endif
以及:
<条件关键字>
<条件为真时执行的语句>
else
<条件为假时执行的语句>
endif
其中条件关键字有 4 个: ifeq、 ifneq、 ifdef 和 ifndef,这四个关键字其实分为两对、 ifeq 与
ifneq、 ifdef 与 ifndef,先来看一下 ifeq 和 ifneq, ifeq 用来判断是否相等, ifneq 就是判断是否不
相等, ifeq 用法如下:
ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’ ,‘ <参数 2>’
ifeq “<参数 1>” , “<参数 2>”
ifeq “<参数 1>” , ‘<参数 2>’
ifeq ‘<参数 1>’ , “<参数 2>”
上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和
“参数 2”可以为函数返回值。 ifneq 的用法类似,只不过 ifneq 是用来了比较“参数 1”和“参
数 2”是否不相等,如果不相等的话就为真。
ifdef 和 ifndef 的用法如下:
ifdef <变量名>
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是
一个函数的返回值。 ifndef 用法类似,但是含义用户 ifdef 相反
7、Makefile函数使用
Makefile 支持函数,类似 C 语言一样, Makefile 中的函数是已经定义好的,我们直接使用,
不支持我们自定义函数。 make 所支持的函数不多,但是绝对够我们使用了,函数的用法如下:
$(函数名 参数集合)
或者
${函数名 参数集合}
可以看出,调用函数和调用普通变量一样,使用符号“$”来标识。参数集合是函数的多个
参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“$”开
头。接下来我们介绍几个常用的函数,其它的函数大家可以参考《跟我一起写 Makefile》这份
文档。
1、函数 subst
函数 subst 用来完成字符串替换,调用形式如下:
$(subst <from>,<to>,<text>)
此函数的功能是将字符串<text>中的<from>内容替换为<to>,函数返回被替换以后的字符
串,比如如下示例:
$(subst zzk,ZZK,my name is zzk)
把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成以后的字符串为“my name
is ZZK”。
2、函数 patsubst
函数 patsubst 用来完成模式字符串替换,使用方法如下:
$(patsubst <pattern>,<replacement>,<text>)
此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来
替换掉, <pattern>可以使用包括通配符“%”,表示任意长度的字符串,函数返回值就是替换后
的字符串。如果<replacement>中也包涵“%”,那么<replacement>中的“%”将是<pattern>中的
那个“%”所代表的字符串,比如:
$(patsubst %.c,%.o,a.c b.c c.c)
将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字
符串为“a.o b.o c.o”。
3、函数 dir
函数 dir 用来获取目录,使用方法如下:
$(dir <names…>)
此函数用来从文件名序列<names>中提取出目录部分,返回值是文件名序列<names>的目录
部分,比如:
$(dir </src/a.c>)
提取文件“/src/a.c”的目录部分,也就是“/src”。
4、函数 notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:
$(notdir <names…>)
此函数用与从文件名序列<names>中提取出文件名非目录部分,比如:
$(notdir </src/a.c>)
提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。
5、函数 foreach
foreach 函数用来完成循环,用法如下:
$(foreach <var>, <list>,<text>)
此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所
包含的表达式。每次<text>都会返回一个字符串,循环的过程中, <text>中所包含的每个字符串
会以空格隔开,最后当整个循环结束时, <text>所返回的每个字符串所组成的整个字符串将会是
函数 foreach 函数的返回值。
6、函数 wildcard
通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,
通配符不会自动展开,这个时候就要用到函数 wildcard,使用方法如下:
$(wildcard PATTERN…)
比如:
$(wildcard *.c)
上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。
关于 Makefile 的相关内容就讲解到这里,本节只是对 Makefile 做了最基本的讲解,确保大
家能够完成后续的学习, Makefile 还有大量的知识没有提到,有兴趣的可以自行参考《跟我一
起写 Makefile》这份文档来深入学习 Makefile。