Makefile书写格式
在大型程序开发时,通常会用到Makefile进行构建(build)。Makefile文件一般规定了源文件怎么去编译(compile),其内部的书写规则如下:
<target> : <prerequisites>
[tab] <commands>
上面第一行冒号前面的部分,叫做目标(target),冒号后面的部分叫做前置条件(prerequisites);第二行必须由一个tab键起首,后面跟着命令(commands)。
目标是必需的,不可省略;前置条件和命令都是可选的,但是两者之中必须至少存在一个。
目标target
一个目标(target)就可构成一条命令。目标通常是文件名,来指明Make命令所要构建的对象。它既可以是一个文件名,也可以是多个,之间用空格分隔即可。
除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。如:
clean:
rm *.o
上面的目标clean,不是一个文件名,而是一个操作的名字,属于“伪目标”,作用是删除所有.o文件。
$ make clean
但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。
为了避免这种情况发生,可以明确声明clean是"伪目标",写法如下
.PHONY : clean
clean:
rm *.o temp
声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。类似于.PHONY的内置名还有许多,此处暂且不表。
如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。
$ make
此代码只执行Makefile文件的第一个目标,当然,若此目标中有前置条件,会先满足前置条件而后执行。
前置条件prerequisites
前置条件通常也是一组文件名,之间用空格分隔。它指定了目标是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),目标就需要重新构建。
比如如下命令:
target.txt : request.txt
cp request.txt target.txt
上面代码中,构建 target.txt 的前置条件是 request.txt 。如果当前目录中,request.txt 已经存在,那么命令make target.txt可以正常运行,否则必须再写一条规则,来生成 request.txt 。
request.txt:
echo "this is the source" > request.txt
上面代码中,request.txt后面没有前置条件,就意味着它跟其他文件都无关,只要这个文件还不存在,每次调用make request.txt,它都会生成。
$ make target.txt
$ make target.txt
上面命令连续执行两次make target.txt。第一次执行会先新建 request.txt,然后再新建 target.txt。第二次执行,Make发现 request.txt 没有变动(时间戳早于 target.txt),就不会执行任何操作,target.txt 也不会重新生成。
如果需要生成多个文件,往往采用:
source : file1 file2 file3
在这个代码中,source是一个伪目标,只有三个前置条件(文件),没有任何命令,这样,直接使用命令make source就可以一次性生成三个文件,方便了一些。
命令commands
命令(commands)表示如何更新目标文件,由一行或多行的Shell命令组成。它是构建目标的具体指令,它的运行结果往往就是生成目标文件。
每行命令都是在一个单独的shell中运行,这些shell之间没有关系,因此,对如下代码:
test:
export foo = bar
echo "foo=[$$foo]"
执行(make test)后,并不能取到foo的值。因为两行命令是在两个不同的进程之间运行的。要想达到预期效果,可以将两个命令写在一行,中间使用分号隔开,或者在换行符前加反斜杠转义,或者加上 .ONESHELL 命令
#将两个命令写在一行,中间使用分号隔开
test:
export foo=bar; echo "foo=[$$foo]"
#在换行符前加反斜杠转义
test:
export foo=bar; \
echo "foo=[$$foo]"
#加上.ONESHELL:命令
.ONESHELL:
var-kept:
export foo=bar;
echo "foo=[$$foo]"
注意:每行命令前必须要有一个tab键,如果想用其他键,可以使用内部变量.RECIPEPREFIX声明。
.RECIPEPREFIX = >
all:
> echo Hello, world
上面的代码即用.RECIPEPREFIX指定了大于号(>)替代tab键。所以,每一行命令的起首变成了大于号,而不是tab键。
Makefile语法
1.注释
#在Makefile中表示注释
2.回声(echoing)
正常情况下,make会打印每条命令,然后再执行, 此机制叫做回声(echoing),比如
test:
#just for a test
执行make test,会出现如下结果:
$ make test
#just for a test
而如果在命令的前面加上@,就可以关闭回声,再次执行make test 就不会有任何输出。
由于在构建过程中,需要了解当前在执行哪条命令,所以通常只在注释和纯显示的echo命令前面加上@。
3.通配符
与bash一致,主要有*,?,以及[…]和~。
通配符 | 作用 |
---|---|
? | 代表一个任意字符 |
* | 代表0个或任意多个字符 |
[ ] | 代表括号中的一个字符 |
[-] | -代表一个范围 |
[^] | ^代表逻辑非,类似正则表达式规则 |
4.模糊匹配
Makefile中可以对文件名进行类似正则运算的匹配,主要的匹配符号是%。
5.变量和赋值符
Makefile中允许使用等号来自定义变量。
txt = hello world
test:
@echo $(txt)
上面的代码中, 变量txt等于hello world,调用时,变量需要放在$()之中。
调用shell变量,需要在美元符号之前再加一个美元符号,因为make命令会对美元符号转义。
test:
@echo $$HOME
事实上,Makefile中一共提供了四个赋值运算符(=、:=、?=、+=)
var = value
# 在执行时扩展,允许递归扩展
var := value
#在定义时扩展
var ?= value
#只有在该变量值为空时才设置值
var += value
#将值追加到变量的尾端
6.内置变量
make命令中有一系列的内置变量,比如$(CC)指向当前使用的编译器,$(MAKE)指向当前使用的Make命令,更多可见其手册。
7.自动变量
(1)$@
$@指代当前目标,就是Make命令当前构建的那个目标。
a.txt b.txt:
touch $@
等同于
a.txt b.txt:
touch a.txt ; touch b.txt
(2)$<
$<指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。
(3)$?
$? 指代比目标更新的所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,其中 p2 的时间戳比 t 新,$?就指代p2。
(4)$^
$^ 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2 。
(5)$*
$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。
(6)$(@D) 和 $(@F)
$(@D) 和 $(@F) 分别指向 $@ 的目录名和文件名。比如,$@是 src/input.c,那么$(@D) 的值为 src ,$(@F) 的值为 input.c。
(7)$(<D) 和 $(<F)
$(<D) 和 $(<F) 分别指向 $< 的目录名和文件名。
8.判断与循环
使用bash语法完成判断与循环。
ifeq($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
上面的代码判断当前编译器是否是gcc,并指定不同的库文件
循环语句
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
9.函数
Makefile中可以使用许多预定义的函数,使用的方法为
var = $(func_name arg1,arg2,...)
#返回值=$(函数名 实参)
Makefile中预定义函数有很多,包括shell,wildcard,subsr等函数,详见其文档。
由于Makefile中自定义函数本质上是一个多行变量,无法直接调用,因此暂按下不表。