Makefile使用指南

什么是Makefile

Makefile定义了一系列的规则来指定,哪些文件须要先编译,哪些文件须要后编译,哪些文件须要从新编译,文件之间有哪些依赖等。Makefile有本身的书写格式、关键字、函数。像C 语言有本身的格式、关键字和函数同样。并且在Makefile中可使用系统shell所提供的任何命令来完成想要的工做。 Makefile带来的好处就是——“自动化编译”,一旦写好,只须要一个make命令,整个工程彻底自动编译,极大的提升了软件开发的效率shell

makefile的文件名

默认的状况下,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
复制代码

Makefile的规则

显示规则

target ... : prerequisites ...
    command
复制代码
  • target:要生成的目标,也能够是一个标签
  • prerequisites:目标所依赖的列表
  • command:任意shell指令,通常用于生成target。必须以[Tab键]开头,也能够和prerequisites在一行,用分号作为分隔

这是一个文件的依赖关系,也就是说,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操作系统

  • test不存在
  • test存在,但test1.o的修改时间比test新
  • test存在,但test2.o的修改时间比test新

把全部依赖关系都在makefile里列举出来以后,执行make命令的时候,就会根据依赖关系自动编译连接了命令行

可是这里的clean又是干什么的呢?它并无其余依赖,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字,好比make clean。这样的方法很是有用,咱们能够在一个makefile中定义不用的编译或是和编译无关的命令,好比程序的打包,程序的备份,等等。code

隐晦规则

每一个.o文件的依赖文件默认会有同名的.c文件,好比有一个target是test.o,那么test.c默认就是test.O的依赖文件,这个是makefile的隐晦规则,是make会自动推导出来的继承

make是怎么工做的?

在默认的方式下,也就是咱们只输入make命令。那么:递归

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件
  2. 若是找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“test”这个文件,并把这个文件做为最终的目标文件
  3. 若是test文件不存在,或是test所依赖的后面的 .o 文件的文件修改时间要比test这个文件新,那么,他就会执行后面所定义的命令来生成test这个文件
  4. 若是test所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,若是找到则再根据那一个规则生成.o文件

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件

makefile中使用变量

变量的基础

为了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=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”
复制代码

让make自动推导

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注释

Makefile中只有行注释,和UNIX的Shell脚本同样,其注释是用“#”字符,如:

# 这是在makefile中的注释1
# 这是在makefile中的注释2
复制代码

引用其它的Makefile

在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
复制代码
相关文章
相关标签/搜索