什么是Makefile
Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,文件之间有哪些依赖等。Makefile有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。
Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
makefile的文件名
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。
当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“–file”参数,如:
1 | make -f Make.Linux |
Makefile的规则
显示规则
1 | target ... : prerequisites ... |
- target:要生成的目标,也可以是一个标签
- prerequisites:目标所依赖的列表
- command:任意shell指令,一般用于生成target。必须以[Tab键]开头,也可以和prerequisites在一行,用分号做为分隔
这是一个文件的依赖关系,也就是说,target依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行(command一定要以Tab键开始,否则编译器无法识别command)。这就是Makefile的规则,也是Makefile中最核心的内容。
举个栗子:
1 | test : test1.o test2.o |
在这里,test是最终的目标,生成test,依赖于test1.o和test2.o也就是说,有以下情况发生时,会执行cc -o test test1.o test2.o指令,以重新生成target
- test不存在
- test存在,但test1.o的修改时间比test新
- test存在,但test2.o的修改时间比test新
把所有依赖关系都在makefile里列举出来之后,执行make命令的时候,就会根据依赖关系自动编译链接了
但是这里的clean又是干什么的呢?它并没有其他依赖,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字,比如make clean
。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
隐晦规则
每个.o文件的依赖文件默认会有同名的.c文件,比如有一个target是test.o,那么test.c默认就是test.O的依赖文件,这个是makefile的隐晦规则,是make会自动推导出来的
make是怎么工作的?
在默认的方式下,也就是我们只输入make命令。那么:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“test”这个文件,并把这个文件作为最终的目标文件
- 如果test文件不存在,或是test所依赖的后面的 .o 文件的文件修改时间要比test这个文件新,那么,他就会执行后面所定义的命令来生成test这个文件
- 如果test所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件
makefile中使用变量
变量的基础
为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,比如我们可以定义一个objects变量,并通过$(objects)的方式来使用这个变量。变量类似与C语言的宏定义,在执行的时候,变量的值会被扩展到被使用的地方
1 | objects = test1.o test2.o |
第一种写法和第二种写法的作用完全是一样的
变量中的变量
在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。
先看第一种方式,也就是简单的使用“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:
1 | foo = $(bar) |
这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,不好的地方,那就是递归定义:
1 | A = $(B) |
为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是“:=”操作符:
1 | x := foo |
追加变量值
我们可以使用“+=”操作符给变量追加值,如:
1 | objects = main.o foo.o bar.o utils.o |
于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)
如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:
1 | variable := value |
等价于:
1 | variable := value |
但如果是这种情况:
1 | variable = value |
由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。
变量的高级用法
变量值的替换
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符还是看一个示例吧:
1 | foo := a.o b.o c.o |
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:
1 | foo := a.o b.o c.o |
把变量的值再当成变量
1 | x = y |
让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令。
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂
清空目标文件的规则
每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁,一般风格是:
1 | .PHONY : clean |
makefile注释
Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,如:
1 | # 这是在makefile中的注释1 |
引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含进来,make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:
1 | include <filename> |
伪目标
最早先的一个例子中,我们提到过一个“clean”的目标。
1 | clean: |
伪目标不会自动被执行,只能显式地调用执行。但是上面伪目标的写法有一个缺陷,若是当前目录下存在有一个文件名为”clean”,那么根据我们的规则,command将不会被执行,因为目标已经存在了,为了解决这个问题,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”
1 | .PHONY : clean |
通过.PHONY,无论是否存在“clean”文件,我们的command都将会被执行了
命令出错
每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。
有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。
为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:
1 | clean: |