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