三、Makefile基础
Makefile基础
Makefile简介
什么是makefile?
如果以前一直使用 IDE来编写 C/C++ 语言的话肯定没有听说过 Makefile 这个东西,因为这些 IDE对makefile文件进行了封装,提供给的是已经经过封装后的图形界面,我们在 IDE 中添加要编译的 C/C++文件,然后点击按钮就完成了编译。而在 Linux 下用的最多的是 GCC 编译器,这是个没有 UI的编译器,因此 Makefile 就需要我们自己来编写了。
Makefile:是描述哪些文件需要编译、哪些需要重新编译的文件; 而make就是工程编译的工具,相当于IDE中图形界面的按钮。
为什么要引入Makefile?
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,但当工程中的文件有几十、上百甚至上万个的时候,用终端输入 GCC 命令的方法显然是不现实的 。这种情况下我们需要借助项目构造工具 make 帮助我们完成这个艰巨的任务。
Makefile语法
Makefile规则
Makefile 的框架是由规则构成的。make 命令执行时先在 Makefile 文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式为:
Makefile规则的语法格式:
1 | target ... : depend ... |
每条规则由三个部分组成分别是目标(target), 依赖(depend) 和命令(command)。
①、目标(target): 规则中的目标,这个目标和规则中的命令是对应的
通过执行规则中的命令,可以生成一个和目标同名的文件
规则中可以有多个命令,因此可以通过这多条命令来生成多个目标,所有目标也可以有很多个
通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标②、依赖(depend): 规则所必需的依赖条件,在规则的命令中可以使用这些依赖。
例如:生成可执行文件的目标文件(*.o)可以作为依赖使用
如果规则的命令中不需要任何依赖,那么规则的依赖可以为空
当前规则中的依赖可以是其他规则中的某个目标,这样就形成了规则之间的嵌套
依赖可以根据要执行的命令的实际需求,指定很多个③、命令(command): 当前这条规则的动作,一般情况下这个动作就是一个 shell 命令
例如:通过某个命令编译文件、生成库文件、进入目录等。
动作可以是多个,命令列表中的每条命令必须以 TAB 键开始,不能使用空格! 并且独占占一行。 如果命令太长,可以使用反斜框(‘\’)作为换行符。
例:通过键盘输入两个数字,计算出它们的乘积;
input.c
1 |
|
input.h
1 |
|
multiply .c
1 | int multiply(int a, int b) |
multiply .h
1 |
|
main.c
1 |
|
工程中有 main.c、 input.c 和 multiply .c 这三个 C 文件和 input.h、 multiply .h 这两个头文件
例1:Makefile 编写
1 | 规则1 main是通过编译 main.o input.o multiply.o |
规则的执行顺序:
需要注意的是在调用 make 命令编译程序的时候,make 会首先找到 Makefile 文件中的第 1 个规则,分析并执行相关的动作。但好多时候要执行的命令中使用的依赖是不存在的,如果使用的依赖不存在,这个动作也就不会被执行。
解决方案:将不存在的依赖作为这个新的规则中的目标,当这条新的规则对应的命令执行完毕,对应的目标就被生成了,同时另一条规则中需要的依赖也就存在了。这样,makefile 中的某一条规则在需要的时候,就会被其他的规则调用,直到 makefile 中的第一条规则中的所有的依赖全部被生成,第一条规则中的命令就可以基于这些依赖生成对应的目标,make 的任务也就完成了。
如上例中:规则1中main.o 、input.o、 multiply.o先没有存在,那么就先执行规则2、规则3、规则4,生成main.o 、input.o、 multiply.o后再执行规则1
Makefile变量
跟C/C++语言一样 Makefile 也支持变量 ;但不像 C/C++语言中的变量有 int、char等各种类型, Makefile 中的变量都是字符串。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
1 | objects = main.o input.o multiply.o |
Makefile变量的赋值符还有其它两个“:=”和“?=”
1、赋值符“=” : 使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值;
2、赋值符“:=” :赋值符“:=”不会使用后面定义的变量,只能使用前面已经定义好的;相当于在C/C++ 变量前添加了const;
3、赋值符“?=” :如果变量没有被赋值,那就使用=号后的值,如果已经定义,那就不改变;
4、变量追加“+=” :Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进去,此时就要使用到符号“+=”
3.2 预定义变量
在 Makefile 中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义。在进行编译的时候,某些条件下 Makefile 会使用这些预定义变量的值进行编译。这些预定义变量的名字一般都是大写的,经常采用的预定义变量如下表所示:
Makefile模式
模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符, a.%.c 就表示以 a.开头,以.c 结束的所有文件。
Makefile自动化变量
Makefile 中的变量除了用户自定义变量和预定义变量外,还有一类自动变量。Makefile 中的规则语句中经常会出现目标文件和依赖文件,自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用。
常用的三种: $@、 $<和$^
使用Makefile模式规则和自动化变量对例1中Makefile文件进行精简:
1 | 规则1 |
Makefile伪目标
Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标的目的:主要是为了避免 Makefile 中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,如:
1 | clean: |
规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。但当出错在工作目录下创建一个名为“clean”的文件时,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,预先设想的清理工程的功能也就无法完成。为了避免这个问题,可以将 clean 声明为伪目标,声明方式如下:
1 | .PHONY : clean |
1 | 规则1 |
clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。
Makefile的条件判断
Makefile 也支持条件判断,语法有两种:
1 | <条件关键字> |
以及:
1 | <条件关键字> |
条件关键字有 4 个:
ifeq、 ifneq、 ifdef 和 ifndef,这四个关键字其实分为两对、 ifeq 与ifneq、 ifdef 与 ifndef。
①、ifeq 和 ifneq
ifeq 用来判断是否相等, ifneq 就是判断是否不相等, ifeq 用法如下:
1 | ifeq (<参数 1>, <参数 2>) |
用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值。 ifneq 的用法类似,只不过 ifneq 是用来了比较“参数 1”和“参数 2”是否不相等,如果不相等的话就为真。
②、ifdef 和 ifndef
1 | ifdef <变量名> |
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是一个函数的返回值。 ifndef 用法类似,但是含义用户 ifdef 相反。
Makefile函数
Makefile 中的函数是已经定义好的,可以直接使用,但不支持自定义函数。
函数的用法:
1 | (函数名 参数集合) |
或者
1 | {函数名 参数集合} |
调用函数和调用普通变量一样,使用符号“$”来标识。参数集合是函数的多个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“$”开头。
常用的Makefile函数:
①、函数 subst
函数 subst 用来完成字符串替换,调用形式如下:
1 | (subst <from>,<to>,<text>) |
此函数的功能是将字符串
②、、函数 patsubst
函数 patsubst 用来完成模式字符串替换,使用方法如下:
1 | (patsubst <pattern>,<replacement>,<text>) |
此函数查找字符串
1 | (patsubst %.c,%.o,a.c b.c c.c) |
将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。
③、、函数 dir
函数 dir 用来获取目录,使用方法如下:
1 | (dir <names…>) |
④、、函数 notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:
1 | (notdir <names…>) |
此函数用与从文件名序列
1 | (notdir </src/a.c>) |
提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。
⑤、、函数 foreach
foreach 函数用来完成循环,用法如下:
1 | (foreach <var>, <list>,<text>) |
此函数的意思就是把参数中的单词逐一取出来放到参数中,然后再执行
包含的表达式。每次
会以空格隔开,最后当整个循环结束时,
函数 foreach 函数的返回值。
⑥、函数 wildcard
通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,
通配符不会自动展开,这个时候就要用到函数 wildcard,使用方法如下:
1 | (wildcard PATTERN…) |
比如:
1 | (wildcard *.c) |
上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。