系列文章目录
介绍
target ... : prerequisites ...
command
...
...
- target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是
一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。 - prerequisites 就是,要生成那个 target 所需要的文件或是目标。
- command 也就是 make 需要执行的命令。(任意的 Shell 命令)
Makefile 里有什么
Makefile 里主要包含了五个东西:显示规则、隐晦规则、变量定义、文件指示和注释。
- 显示规则。显示规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则。由于我们的 make 有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 所支持的。
- 变量的定义。在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像 C 语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像 C 语言中的预编译#if 一样;还有就是定义一个多行的命令。
- 注释。Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用 “#” 字符,这个就像 C/C++ 中的 “//” 一样。如果你要在你的 Makefile 中使用 “#” 字符,可以用反斜框进行转义,如:“\#”。
最后,还值得一提的是,在 Makefile 中的命令,必须要以 Tab 键开始。
Makefile 的文件名
可以为:“Makefile”、“makefile”、“GNUmakefile” 。 推荐第一个。
指定特定的 Makefile,使用 make 的 “-f” 和 “–file” 参数。
make -f Make.Linux
# 或
make --file Make.AIX。
引用其它的 Makefile
include foo.make *.mk $(bar)
# 等价于:
include foo.make a.mk b.mk c.mk e.mk f.mk
make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
- 如果 make 执行时,有 “-I” 或 “–include-dir” 参数,那么 make 就会在这个参数所指定的目录下去寻找。
- 如果目录 <prefix>/include(一般是:/usr/local/bin 或 /usr/include)存在的话,make 也会去找。
-include <filename>
其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼容的相关命令是 sinclude
,其作用和这一个是一样的。
make 的工作方式
GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)
- 读入所有的 Makefile。
- 读入被 include 的其它 Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
书写规则
规则的语法
targets : prerequisites
command
...
# 或是这样:
targets : prerequisites ; command
command
...
targets 是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。
command 是命令行,如果其不与 “target:prerequisites” 在一行,那么,必须以 Tab 开头,如果和 prerequisites 在一行,那么可以用分号做为分隔。(见上)
prerequisites 也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是 “过时的” ,被认为是需要重生成的。这个在前面已经讲过了。
如果命令太长,你可以使用反斜框(‘\’)作为换行符。make 对一行上有多少个字符没有限制。规则告诉 make 两件事,文件的依赖关系和如何成成目标文件。
伪目标
PHONY: clean
clean:
rm *.o temp
“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。
多目标
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
# 上述规则等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
静态模式
# 语法
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
- targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
- target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
- prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明了我们的目标从 $object
中获取,“\%.o”
表明要所有以 “.o” 结尾的目标,也就是 “foo.o bar.o” ,也就是变量 $object 集合的模式,而依赖模式 “\%.c”
则取模式 “%.o” 的 “%”,也就是 “foo bar” ,并为其加下 “.c” 的后缀,于是,我们的依赖目标就是 “foo.c bar.c” 。而命令中的 “$<”
和 “$@”
则是自动化变量,“$<”
表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”
表示目标集(也就是 “foo.o bar.o” )。
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))
表示调用 Makefile 的 filter 函数,只要其中模式为“%.o”的内容。
书写命令
显示命令
@echo 正在编译 XXX 模块......
当 make 执行时,会输出 “正在编译 XXX 模块…” 字串,但不会输出命令,如果没有 “@” ,那么,make 将输出:
echo 正在编译 XXX 模块…
正在编译 XXX 模块…
如果 make 执行时,带入 make 参数 “-n”
或 “--just-print”
,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
而 make 参数 “-s”
或 “--slient”
则是全面禁止命令的显示。
命令执行
需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。
# 示例一:
exec:
cd /home/hchen
pwd
# 示例二:
exec:
cd /home/hchen; pwd
当我们执行 “make exec” 时,第一个例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,而第二个例子中,cd 就起作用了,pwd 会打印出 “/home/hchen” 。
命令出错
如果一个规则中的某个命令出错了(命令退出码非零),那么 make 就会终止执行当前规则,这将有可能终止所有规则的执行。如果想忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号 “-”
(在Tab 键之后),标记为不管命令出不出错都认为是成功的。如:
clean:
-rm -f *.o
还有一个全局的办法是,给 make 加上 “-i”
或是 “--ignore-errors”
参数,那么, Makefile 中所有命令都会忽略错误。
还有一个要提一下的 make 的参数的是 “-k”
或是 “--keep-going”
,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
嵌套执行 make
我们有一个子目录叫 subdir,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:
subsystem:
cd subdir && $(MAKE)
# 其等价于:
subsystem:
$(MAKE) -C subdir
定义 $(MAKE)
宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入 “subdir” 目录,然后执行 make 命令。
我们把这个 Makefile 叫做 “总控 Makefile” ,总控 Makefile 的变量可以传递到下级的 Makefile 中(如果你显示的声明),但是不会覆盖下层的 Makefile 中所定义的变量,除非指定了 “-e”
参数。
- 如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明:
export <variable ...>
- 如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:
unexport <variable ...>
如:
export variable = value
# 其等价于:
# variable = value
# export variable
export variable := value
export variable += value
如果你要传递所有的变量,那么,只要一个 export
就行了。后面什么也不用跟,表示传递所有的变量。
需要注意的是,有两个变量,一个是 SHELL
,一个是 MAKEFLAGS
,这两个变量不管你是否 export,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make 的参数信息,如果我们执行 “总控 Makefile”时有 make 参数或是在上层 Makefile 中定义了这个变量,那么 MAKEFILES 变量将会是这些参数,并会传递到下层 Makefile 中,这是一个系统级的环境变量。
如果你不想往下层传递参数,那么,你可以这样来:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
定义命令包
如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以 “define”
开始,以 “endef”
结束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
$(run-yacc)
使用变量
定义变量
- = ,递归扩展赋值
# 定义变量
objects = program.o foo.o utils.o
- := ,立即扩展赋值
# 变量是可以使用后面的变量来定义
foo = $(bar)
bar = $(ugh)
ugh = Huh?
# 前面的变量不能使用后面的变量
x := foo
y := $(x) bar
x := later
# y ==> foo bar ; x ==> later
- ?= ,条件赋值
# 如果 FOO 没有被定义过,那么变量 FOO 的值就是 “bar” ,
# 如果 FOO 先前被定义过,那么这条语将什么也不做。
FOO ?= bar
- += ,追加赋值
# 追加变量值
objects = main.o foo.o bar.o utils.o
objects += another.o
# $(objects) ==> “main.o foo.o bar.o utils.o another.o”
变量高级用法
- 变量值的替换
$(var:a=b)
# 或
${var:a=b}
# 把变量 “var” 中所有以 “a” 字串 “结尾” 的 “a” 替换成 “b” 字串。
foo := a.o b.o c.o
bar := $(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))
# 推导 a := $($(x)) ==> a := $(y) ==> a := z
override 阻止命令行覆盖
override <变量名> [= | := | ?= | +=] <值>
override CC = gcc
多行变量
见 定义变量包
目标变量
语法:
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
# <variable-assignment> 可以是各种赋值表达式,如 =, :=, +=, ?=
示例:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
这个示例中,不管全局的 $(CFLAGS)
的值是什么,在 prog
目标,以及其所引发的所有规则中(prog.o foo.o bar.o 的规则),$(CFLAGS)
的值都是 “-g” 。
模式变量
语法:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
# <variable-assignment> 可以是各种赋值表达式,如 =, :=, +=, ?=
示例:
%.o : CFLAGS = -O
给所有以 [.o]
结尾的目标定义目标变量。
使用条件判断
- 语法
<conditional-directive>
<text-if-true>
endif
# 以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中 <conditional-directive> 表示条件关键字,可以为 ifeq
, ifneq
, ifdef
, ifndef
。
- ifeq
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
- ifneq
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
- ifdef
ifdef <variable-name>
- ifndef
ifndef <variable-name>
使用函数
函数的调用语法
$(<function> <arguments>)
# 或是
${<function> <arguments>}
这里,<function>
就是函数名,make 支持的函数不多。<arguments>
是函数的参数,参数间以逗号 “,”
分隔,而函数名和参数之间以 “空格”
分隔。
示例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)
的值是一个逗号。$(space)
使用了 $(empty)
定义了一个空格,$(foo)
的值是 “a b c” ,$(bar)
调用了函数 “subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo)
中的空格替换成逗号,所以 $(bar)
的值是 “a,b,c” 。
字符串处理函数
subst
$(subst <from>,<to>,<text>)
# 名称:字符串替换函数——subst。
# 功能:把字串 <text> 中的 <from> 字符串替换成 <to> 。
# 返回:函数返回被替换过后的字符串。
patsubst
$(patsubst <pattern>,<replacement>,<text>)
# 名称:模式字符串替换函数——patsubst。
# 功能:查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否
# 符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换。这里, <pattern> 可
# 以包括通配符 “%” ,表示任意长度的字串。如果 <replacement> 中也包含 “%” ,那么,
# <replacement> 中的这个 “%” 将是 <pattern> 中的那个 “%” 所代表的字串。(可以用 “\”
# 来转义,以 “\%” 来表示真实含义的 “%” 字符)
# 返回:函数返回被替换过后的字符串。
# 示例:
$(patsubst %.c,%.o,x.c.c bar.c)
# 把字串 “x.c.c bar.c” 符合模式 [%.c] 的单词替换成 [%.o] ,返回结果是 “x.c.o bar.o”
strip
$(strip <string>)
# 名称:去空格函数——strip。
# 功能:去掉 <string> 字串中开头和结尾的空字符。
# 返回:返回被去掉空格的字符串值。
# 示例:
$(strip a b c )
# 把字串 “a b c ” 去掉开头和结尾的空格,结果是 “a b c” 。
findstring
$(findstring <find>,<in>)
# 名称:查找字符串函数——findstring。
# 功能:在字串 <in> 中查找 <find> 字串。
# 返回:如果找到,那么返回 <find> ,否则返回空字符串。
# 示例:
$(findstring a,a b c)
$(findstring a,b c)
# 第一个函数返回 “a” 字符串,第二个返回 “” 字符串(空字符串)
filter
$(filter <pattern...>,<text>)
# 名称:过滤函数——filter。
# 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <pattern> 的单
# 词。可以有多个模式。
# 返回:返回符合模式 <pattern> 的字串。
# 示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
# $(filter %.c %.s,$(sources)) 返回的值是 “foo.c bar.c baz.s”。
filter-out
$(filter-out <pattern...>,<text>)
# 名称:反过滤函数——filter-out。
# 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单
# 词。可以有多个模式。
# 返回:返回不符合模式 <pattern> 的字串。
# 示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) # 返回值是 “foo.o bar.o” 。
sort
$(sort <list>)
# 名称:排序函数——sort。
# 功能:给字符串 <list> 中的单词排序(升序)。
# 返回:返回排序后的字符串。
# 示例:$(sort foo bar lose) 返回 “bar foo lose” 。
# 备注:sort 函数会去掉 <list> 中相同的单词。
word
$(word <n>,<text>)
# 名称:取单词函数——word。
# 功能:取字符串 <text> 中第 <n> 个单词。(从一开始)
# 返回:返回字符串 <text> 中第 <n> 个单词。如果 <n> 比 <text> 中的单词数要大,那么
# 返回空字符串。
# 示例:$(word 2, foo bar baz) 返回值是 “bar” 。
wordlist
$(wordlist <s>,<e>,<text>)
# 名称:取单词串函数——wordlist。
# 功能:从字符串 <text> 中取从 <s> 开始到 <e> 的单词串。<s> 和 <e> 是一个数字。
# 返回:返回字符串 <text> 中从 <s> 到 <e> 的单词字串。如果 <s> 比 <text> 中的单词数
# 要大,那么返回空字符串。如果 <e> 大于 <text> 的单词数,那么返回从 <s> 开始,到 <text>
# 结束的单词串。
# 示例: $(wordlist 2, 3, foo bar baz) 返回值是 “bar baz”。
words
$(words <text>)
# 名称:单词个数统计函数——words。
# 功能:统计 <text> 中字符串中的单词个数。
# 返回:返回 <text> 中的单词数。
# 示例:$(words, foo bar baz) 返回值是 “3” 。
# 备注:如果我们要取 <text> 中最后的一个单词,我们可以这样:
# $(word $(words<text>),<text>)。
firstword
$(firstword <text>)
# 名称:首单词函数——firstword。
# 功能:取字符串 <text> 中的第一个单词。
# 返回:返回字符串 <text> 的第一个单词。
# 示例:$(firstword foo bar) 返回值是 “foo” 。
# 备注:这个函数可以用 word 函数来实现:$(word 1,<text>) 。
文件名操作函数
dir
$(dir <names...>)
# 名称:取目录函数——dir。
# 功能:从文件名序列 <names> 中取出目录部分。目录部分是指最后一个反斜杠(“/”)
# 之前的部分。如果没有反斜杠,那么返回“./”。
# 返回:返回文件名序列<names>的目录部分。
# 示例: $(dir src/foo.c hacks) 返回值是 “src/ ./” 。
notdir
$(notdir <names...>)
# 名称:取文件函数——notdir。
# 功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)
# 之后的部分。
# 返回:返回文件名序列<names>的非目录部分。
# 示例: $(notdir src/foo.c hacks) 返回值是 “foo.c hacks” 。
suffix
$(suffix <names...>)
# 名称:取后缀函数——suffix。
# 功能:从文件名序列<names>中取出各个文件名的后缀。
# 返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
# 示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是 “.c .c” 。
basename
$(basename <names...>)
# 名称:取前缀函数——basename。
# 功能:从文件名序列<names>中取出各个文件名的前缀部分。
# 返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
# 示例:$(basename src/foo.c src-1.0/bar.c hacks)
# 返回值是 “src/foo src-1.0/bar hacks” 。
addsuffix
$(addsuffix <suffix>,<names...>)
# 名称:加后缀函数——addsuffix。
# 功能:把后缀<suffix>加到<names>中的每个单词后面。
# 返回:返回加过后缀的文件名序列。
# 示例:$(addsuffix .c,foo bar) 返回值是 “foo.c bar.c” 。
addprefix
$(addprefix <prefix>,<names...>)
# 名称:加前缀函数——addprefix。
# 功能:把前缀<prefix>加到<names>中的每个单词后面。
# 返回:返回加过前缀的文件名序列。
# 示例:$(addprefix src/,foo bar)返回值是 “src/foo src/bar” 。
join
$(join <list1>,<list2>)
# 名称:连接函数——join。
# 功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个
# 数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单
# 词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
# 返回:返回连接过后的字符串。
# 示例:$(join aaa bbb , 111 222 333) 返回值是 “aaa111 bbb222 333” 。
foreach 函数
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数 <list>
中的单词逐一取出放到参数 <var>
所指定的变量中,然后再执行 <text>
所包含的表达式。每一次 <text>
会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
示例:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)
中的单词会被挨个取出,并存到变量 “n”
中,“$(n).o”
每次根据 “$(n)”
计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,$(files)
的值是 “a.o b.o c.o d.o” 。
if 函数
$(if <condition>,<then-part>)
# 或是
$(if <condition>,<then-part>,<else-part>)
call 函数
call 函数是唯一一个可以用来创建新的参数化的函数。
$(call <expression>,<parm1>,<parm2>,<parm3>...)
当 make 执行这个函数时,<expression>
参数中的变量,如 $(1)
,$(2)
,$(3)
等,会被参数 <parm1>
,<parm2>
,<parm3>
依次取代。而的返回值就是 call 函数的返回值。
示例:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
# foo 的值就是 “b a”
origin 函数
origin 函数只是告诉你你的这个变量是哪里来的。
$(origin <variable>)
注意,<variable>
是变量的名字,不应该是引用。所以你最好不要在 <variable>
中使用 “$”
字符。Origin 函数会以其返回值来告诉你这个变量的 “出生情况” ,下面,是 origin函数的返回值:
“undefined”
如果<variable>
从来没有定义过,origin 函数返回这个值 “undefined” 。“default”
如果<variable>
是一个默认的定义,比如 “CC” 这个变量,这种变量我们将在后面讲述。“environment”
如果<variable>
是一个环境变量,并且当 Makefile 被执行时,“-e” 参数没有被打开。“file”
如果<variable>
这个变量被定义在 Makefile 中。“command line”
如果<variable>
这个变量是被命令行定义的。“override”
如果<variable>
是被 override 指示符重新定义的。“automatic”
如果<variable>
是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。
示例:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
如果 bletch 变量来源于环境,那么我们就把之重定义。
shell 函数
shell 函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令 awk,sed 等等命令来生成一个变量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
控制 make 的函数
make 提供了一些函数来控制 make 的运行。通常,你需要检测一些运行 Makefile 时的运行时信息,并且根据这些信息来决定,你是让 make 继续执行,还是停止。
$(error <text ...>)
产生一个致命的错误,<text ...>
是错误信息。注意,error 函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:
# 示例一:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
#示例二:
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)
示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err 被执行时才发生 error 调用。
$(warning <text ...>)
这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而 make 继续执行。
make 的运行
make 的退出码
make 命令执行后有三个退出码:
- 0 —— 表示成功执行。
- 1 —— 如果 make 运行时出现任何错误,其返回 1。
- 2 —— 如果你使用了 make 的“-q”选项,并且 make 使得一些目标不需要更新,那么返回 2。
检查规则
有时候,我们不想让我们的 makefile 中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用 make 命令的下述参数:
“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试 makefile 很有用处。
“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make 假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make 会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。
隐含规则
隐含变量
代表命令的变量
AR
函数库打包程序,可创建静态库.a文档。默认是“ar”。AS
汇编程序。默认是“as”。CC
C编译程序。默认是“cc”。CXX
C++编译程序。默认是“g++”。CPP
C程序的预处理器(输出是标准输出设备)。默认是“$(CC) -E”。YACC
Yacc文法分析器(针对于C程序)。默认命令是“yacc”。RM
删除命令。默认是“rm -f”- …
命令参数的变量
下边的是代表命令执行参数的变量。如果没有给出默认值则默认值为空。
ARFLAGS
执行“AR”命令的命令行参数。默认值是 “rv” 。ASFLAGS
执行汇编语器“AS”的命令行参数(明确指定 “.s” 或 “.S” 文件时)CFLAGS
执行 “CC” 编译器的命令行参数(编译 .c 源文件的选项)。CXXFLAGS
执行 “g++” 编译器的命令行参数(编译 .cc 源文件的选项)。CPPFLAGS
执行C预处理器 “cc -E” 的命令行参数(C 和 Fortran 编译器会用到)。LDFLAGS
链接器(如:“ld”)参数。YFLAGS
Yacc文法分析器参数。- …
模式规则
模式规则示例
下面这个例子表示了,把所有的 [.c] 文件都编译成 [.o] 文件。
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
其中,“$@”
表示所有的目标的挨个值,“$<”
表示了所有依赖目标的挨个值。这些奇怪的变量我们叫 “自动化变量” 。
自动化变量
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@”就是匹配于目标中模式定义的集合。$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 “foo.a(bar.o)”,那么,“$%”
就是 “bar.o” ,“$@”
就是 “foo.a” 。如果目标不是函数库文件(Unix 下是 [.a] ,Windows 下是 [.lib] ),那么,其值为空。$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即“%”
)定义的,那么“$<”
将是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?
所有比目标新的依赖目标的集合。以空格分隔。$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。$+
这个变量很像“$^”
,也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*
这个变量表示目标模式中“%”
及其之前的部分。如果目标是 “dir/a.foo.b” ,并且目标的模式是 “a.%.b” ,那么,“$*”
的值就是 “dir/a.foo” 。这个变量对于构造有关联的文件名是比较有较。
- 如果目标中没有模式的定义,那么
“$*”
也就不能被推导出,但是,如果目标文件的后缀是 make 所识别的,那么“$*”
就是除了后缀的那一部分。例如:如果目标是 “foo.c” ,因为 “.c” 是 make 所能识别的后缀名,所以,“$*”
的值就是 “foo” 。这个特性是 GNU make 的,很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用“$*”
,除非是在隐含规则或是静态模式中。 - 如果目标中的后缀是 make 所不能识别的,那么
“$*”
就是空值。
- 如果目标中没有模式的定义,那么
重建内嵌隐含规则
一个隐含规则,我们可以对它进行重建。重建一个隐含规则时,需要使用相同的目标和依赖模式,命令可以不同(重新指定规则的命令)。这样就可以替代有相同目标和依赖的那个 make 内嵌规则,替代之后隐含规则可能被使用的顺序由它在Makefile中的位置决定。例如通常 Makefile 中可能会包含这样一个规则:
%.o : %.c
$(CC) $(CFLAGS) –D__DEBUG__ $< -o $@
它替代了编译.c文件的内嵌隐含规则。
也可以取消一个内嵌的隐含规则。同样需要定义一个和隐含规则具有相同目标和依赖的规则,但这个规则没有命令行。例如下边的这个规则取消了编译 .s 文件的内嵌规则。
%.o : %.s