概述
本文将介绍Makefile种注释、回显、通配符、变量、循环判断、函数
注释
Makefile中只有单行注释,没有多行注释,注释以 # 开头。以下Makefile注释片段节选自的Makefile
# Makefile for installing Lua# See doc/readme.html for installation and customization instructions.# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================# Your platform. See PLATS for possible values.PLAT= none
echoing(回显)
通常,make在执行命令行之前会把要执行的命令行进行输出。我们称之为“回显”,就好像我们输入命令执行一样。
@
如果要执行的命令行以字符“@”开始,则make在执行时这个命令就不会被回显。典型的用法是我们在使用“echo”命令输出一些信息时。如:
@echo 开始编译XXX模块......
当make执行时,将输出“开始编译XXX模块......”这个信息。如果在命令行之前没有字符“@”,那么,make的输出就是:
echo编译XXX模块......编译XXX模块......
“-n”或“--just-print”
如果使用make的命令行参数“-n”或“--just-print”,那么make执行时只显示所要执行的命令,但不会真正的去执行这些命令。只有在这种情况下make才会打印出所有make需要执行的命令,其中也包括了使用“@”字符开始的命令。这个选项对于我们调试Makefile非常有用,使用这个选项我们可以按执行顺序打印出Makefile中所有需要执行的命令。
“-s”或“--slient”
make参数“-s”或“--slient”则是禁止所有执行命令的显示,就好像所有的命令行均使用“@”开始一样。
.SILENT
这个关键字的行为很像.PHONY,.PHONY标记的target可以理解成一个无条件执行的动作。被.SILENT标价的target,为完成该target执行的所有command都是没有回显的。
.SILENT:clean.PHONY:cleanclean: rm -rf *.o
很显然这里面控制回显最灵活的方式就是@,推荐使用“@”来控制命令的回显。
通配符(Wildcard )
概述
Makefile中使用的通配符与Bash下面的filename wildcards一样,但似乎Makefile主要使用‘*’, ‘?’ 和 ‘[…]’
~字符也有特殊意义,~ 或者 ~/file name 代表home directory
~ 展开为 /home/you 你的家目录~/bin 展开为 /home/you/bin 你的加目录下边的bin目录
~后面不接/则表示别人的家目录
~john 展开为 /home/john john的家目录~john/bin 展开为 /home/john/bin john的家目录下边的bin目录
targets 和 prerequisites中的通配符,有make工具负责展开。commands中的通配符则由shell负责展开。除此之外的其他情况,要想展开通配符则需要显式调用wildcard
函数。
由于*默认情况下具有通配符的特殊含义,如果需要按照普通字符理解他,则需要使用 \* 转义。
通配符举例
在commands中使用通配符
此时通配符展开工作由shell负责
clean: rm -f *.o
在prerequisites 中使用通配符
此时通配符展开由make负责,在target中也可以使用通配符,依然是由amke负责展开。
print: *.c lpr -p $? touch print
这个例子除了表明通配符用法外,同时展示了empty target的用法。empty target是phony target的变体,empty target中的target可以存在,也可以不存在。其特色是在commands最后会touch更新target。
在定义变量时使用通配符
objects = *.o
变量objects的值就是*.o,但是当你把objects的值用于target、prerequisite时,make工具会展开;用于commands时,shell会展开。表面看上去,这似乎没啥问题,看如下代码
objects = *.ofoo : $(objects) cc -o foo $(CFLAGS) $(objects)
objects的值就是*.o,单独看他就是一个名字古怪的文件。但是用在target、prerequisite、commands时,make或shell会自动将其展开为有意义的具体xxx.o文件名,因此上面代码看上去没有问题。但是如果,当前目录下并没有.o文件,target、prerequisite、commands展开时也找不到.o文件,他就会找*.o这个文件,当然*.o文件也是没有的,于是会报"cannot figure out how to make *.o."错误。
解决这个陷阱的方法是使用wildcard函数
wildcard函数通常和patsubst函数一起使用,这是因为在函数内部通配符也不会自动展开
objects := $(patsubst %.c,%.o,$(wildcard *.c))foo : $(objects) cc -o foo $(objects)
变量
自定义变量与赋值符
Makefile 允许使用等号自定义变量。
var = Hello Worldtest: @echo $(var)
上面代码中,变量 var等于 Hello World。调用时,变量需要放在 $( ) 之中。
调用Shell变量,需要在美元符号前,再加一个美元符号,这是因为Make命令会对美元符号转义。
test: @echo $$HOME
有时,变量的值可能指向另一个变量。
v1 = $(v2)
上面代码中,变量 v1 的值是另一个变量 v2。这时会产生一个问题,v1 的值到底在定义时扩展(静态扩展),还是在运行时扩展(动态扩展)?如果 v2 的值是动态的,这两种扩展方式的结果可能会差异很大。
为了解决类似问题,Makefile一共提供了四个赋值运算符 (=、:=、?=、+=),它们的区别请看。
VARIABLE = value# 在执行时扩展,允许递归扩展。VARIABLE := value# 在定义时扩展。VARIABLE ?= value# 只有在该变量为空时才设置值。VARIABLE += value# 将值追加到变量的尾端。
内置变量(Implicit Variables)
Make命令提供一系列内置变量,(感觉上和gcc/g++的预定义宏差不多)比如,$(CC) 指向当前使用的编译器,$(MAKE) 指向当前使用的Make工具。这主要是为了跨平台的兼容性,详细的内置变量清单见。
output: $(CC) -o output input.c
自动变量(Automatic Variables)
Make命令还提供一些自动变量,它们的值与当前规则有关。主要有以下几个。
(1)$@
$@指代当前目标,就是Make命令当前构建的那个目标。比如,make foo
的 $@ 就指代foo。
a.txt b.txt: touch $@
等同于下面的写法。
a.txt: touch a.txtb.txt: touch b.txt
(2)$<
$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。
a.txt: b.txt c.txt cp $< $@
等同于下面的写法。
a.txt: b.txt c.txt cp b.txt a.txt
(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) 分别指向 $< 的目录名和文件名。
所有的自动变量清单,请看。下面是自动变量的一个例子。
dest/%.txt: src/%.txt @[ -d dest ] || mkdir dest cp $< $@
上面代码将 src 目录下的 txt 文件,拷贝到 dest 目录下。首先判断 dest 目录是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目标文件(dest/%.txt)。
判断和循环
循环和判断应用在commands处,而commands的解析则有shell负责。因此Makefile的循环和判断其实也就是shell的循环和判断,其语法与shell完全一样。
ifeq ($(CC),gcc) libs=$(libs_for_gcc)else libs=$(normal_libs)endif
上面代码判断当前编译器是否 gcc ,然后指定不同的库文件。
LIST = one two threeall: for i in $(LIST); do \ echo $$i; \ done# 等同于all: for i in one two three; do \ echo $i; \ done
上面代码的运行结果。
onetwothree
函数
Makefile 还可以使用函数,格式如下。
$(function arguments)# 或者${ function arguments}
Makefile提供了许多,可供调用。下面是几个常用的内置函数。
(1)shell 函数
shell 函数用来执行 shell 命令
srcfiles := $(shell echo src/{ 00..99}.txt)
(2)wildcard 函数
wildcard 函数用来在 Makefile 中,替换 Bash 的通配符。
srcfiles := $(wildcard src/*.txt)
(3)subst 函数
subst 函数用来文本替换,格式如下。
$(subst from,to,text)
下面的例子将字符串"feet on the street"替换成"fEEt on the strEEt"。
$(subst ee,EE,feet on the street)
下面是一个稍微复杂的例子。
comma:= ,empty:=# space变量用两个空变量作为标识符,当中是一个空格space:= $(empty) $(empty)foo:= a b cbar:= $(subst $(space),$(comma),$(foo))# bar is now `a,b,c'.
(4)patsubst函数
patsubst 函数用于模式匹配的替换,格式如下。
$(patsubst pattern,replacement,text)
下面的例子将文件名"x.c.c bar.c",替换成"x.c.o bar.o"。
$(patsubst %.c,%.o,x.c.c bar.c)
(5)替换后缀名
替换后缀名函数的写法是:变量名 + 冒号 + 后缀名替换规则。它实际上patsubst函数的一种简写形式。
min: $(OUTPUT:.js=.min.js)
上面代码的意思是,将变量OUTPUT中的后缀名 .js 全部替换成 .min.js 。