详细内容见 《GNU make》 8 Functions for Transforming Text 章节。
函数允许您在 makefile 中进行文本处理,以计算要操作的文件或配方中要使用的命令。函数调用中使用函数,在函数调用中,您可以提供函数的名称和一些文本(参数),以便函数进行操作。函数的处理结果在调用时被替换到 makefile 中,就像变量可能被替换一样。
函数调用类似于变量引用。它可以出现在变量引用出现的任何地方,并且使用与变量引用相同的规则进行扩展。函数调用如下所示:
$(function arguments)
或者像这样:
${function arguments}
这里的函数是一个函数名;属于 make 的简短名称列表之一。实际上,您还可以通过使用调用内置函数创建自己的函数。
参数是函数的参数。它们与函数名之间用一个或多个空格或制表符分隔,如果有多个参数,则用逗号分隔。这样的空格和逗号不是参数值的一部分。用于包围函数调用的分隔符,无论是圆括号还是大括号,只能以匹配的对出现在参数中;其他类型的分隔符可以单独出现。如果参数本身包含其他函数调用或变量引用,则最明智的方法是对所有引用使用相同类型的分隔符;写‘$(subst a,b,$(x))
’,而不是‘$(subst a,b,${x})
’。这是因为它更清晰,并且只匹配一种类型的分隔符来查找引用的末尾。
为每个参数编写的文本通过替换变量和函数调用来处理,以生成参数值,该参数值是函数作用的文本。替换按参数出现的顺序进行。
逗号和不匹配的括号或大括号不能在写入时出现在参数的文本中;前导空格不能出现在第一个参数的文本中。可以通过变量替换将这些字符放入参数值中。首先定义变量逗号和空格,它们的值是独立的逗号和空格字符,然后将这些变量替换到需要这些字符的地方,如下所示:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now ‘a,b,c’.
在这里,subst 函数通过 foo 的值用逗号替换每个空格,并替换结果。
对 text 文本执行文本替换:每次出现的 from 都替换为 to。对于函数调用结构被替换。例如:
$(subst ee,EE,feet on the street)
生成值 ‘fEEt on the strEEt’。
makefile 文件内容如下:
all:
@echo '$(subst ee,EE,feet on the street)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
fEEt on the strEEt
onlylove@ubuntu:~/My/makefile/05$
在 text 中查找与 pattern 匹配的空格分隔单词,并将其替换为 replacement。此处 pattern 可能包含一个充当通配符的 “%”,用于匹配单词中任意数量的任意字符。如果 replacement 还包含 “%”,则 “%” 将替换为与 pattern 中的 “%” 匹配的 text。只有 pattern 和 replacement 中的第一个"%“以这种方式处理;任何后续的”%"保持不变。
patsubst 函数调用中的 “%” 字符可以用前面的反斜杠 (’’) 括起来。反斜杠可以用更多的反斜杠引用,否则会引用 “%” 字符。引用 “%” 字符或其他反斜杠的反斜杠在比较文件名或替换为它之前从模式中删除。没有引用 “%” 字符危险的反斜杠不会受到干扰。例如,模式 the%weird\%pattern\ 在可操作的 “%” 字符之前有 ‘the%weird\’,在它后面有 ‘pattern\’。最后两个反斜杠不受影响,因为它们不能影响任何%字符。
单词之间的空白被折叠成一个空格字符;前面和后面的空格被丢弃。单词之间的空白被折叠成一个空格字符;前面和后面的空格被丢弃。
例如:
$(patsubst %.c,%.o,x.c.c bar.c)
生成值:‘x.c.o bar.o’。
替换引用是获得patsubst函数效果的一种更简单的方法:
$(var:pattern=replacement)
等效于
$(patsubst pattern,replacement,$(var))
第二个简写方法简化了 patsubst 最常见的用法之一:替换文件名末尾的后缀。
$(var:suffix=replacement)
相当于
$(patsubst %suffix,%replacement,$(var))
例如,您可能有一个目标文件列表:
objects = foo.o bar.o baz.o
要获取相应的源文件列表,您可以简单地编写以下代码:
$(objects:.o=.c)
而不是用一般形式:
$(patsubst %.o,%.c,$(objects))
makefile 文件内容如下:
val = a.c.c b.c.c c.c d.c
var = d.c.c f.c e.c.c g.c
all:
@echo '$(patsubst %.c,%.o,$(val))'
@echo $(\r)
@echo '$(val: %.c=%.o)'
@echo $(\r)
@echo '$(val:%.c=%.o)'
@echo $(\r)
@echo '$(var:.c=.o)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
a.c.o b.c.o c.o d.o
a.c.c b.c.c c.c d.c
a.c.o b.c.o c.o d.o
d.c.o f.o e.c.o g.o
onlylove@ubuntu:~/My/makefile/05$
从字符串中删除前导空格和尾随空格,并将一个或多个空格字符的每个内部序列替换为单个空格。因此,‘$(strip a b c )
’的结果是‘a b c
’。
函数 strip 在与条件结合使用时非常有用。当使用ifeq或ifneq将某个字符串与空字符串进行比较时,通常需要一个只有空格的字符串来匹配空字符串。
因此,以下几点可能无法获得预期的结果:
.PHONY: all
ifneq "$(needs_made)" ""
all: $(needs_made)
else
all:;@echo 'Nothing to make!'
endif
在 ifneq 指令中用函数调用 ‘$(strip $(needs_made))
’ 替换变量引用 ‘$(needs_made)
’ 会使它更加健壮。
makefile 文件内容如下:
all:
@echo \"'$(strip a b c d )'\"
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
"a b c d"
onlylove@ubuntu:~/My/makefile/05$
在 in 中搜索 find 的出现。如果出现,则值为 find;否则为空。您可以使用此函数 in 作为一个条件来测试给定字符串中是否存在特定的子字符串。因此,这两个例子:
$(findstring a,a b c)
$(findstring a,b c)
分别生成值 ‘a’ 和 ‘’(空字符串)。
makefile 文件内容如下:
all:
@echo \"'$(findstring a,a b c)'\"
@echo $(\r)
@echo \"'$(findstring a,b c)'\"
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
"a"
""
onlylove@ubuntu:~/My/makefile/05$
返回 text 中与任何 pattern 单词匹配的所有以空格分隔的单词,删除任何不匹配的单词。patterns 是用%写的,就像上面patsubst函数中使用的 patterns 一样。
filter 函数可用于分离出变量中不同类型的字符串(如文件名)。例如:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
表示 foo 依赖于 foo.c、bar.c、baz.s 和 ugh.h,但只应在编译器的命令中指定 foo.c、bar.c和baz.s。
makefile 文件内容:
sources := foo.c bar.c baz.s ugh.h
all:
@echo '$(filter %.c %.s,$(sources))'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
foo.c bar.c baz.s
onlylove@ubuntu:~/My/makefile/05$
返回 text 中所有不匹配 pattern 中的任何一个单词的以空格分隔的单词,删除匹配一个或多个单词的单词。这和 filter 函数正好相反。
例如:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
以下内容将生成一个列表,其中不包含在"mains"中的所有对象文件:
$(filter-out $(mains),$(objects))
makefile 文件内容如下:
objects = main1.o foo.o main2.o bar.o
mains = main1.o main2.o
all:
@echo '$(filter-out $(mains),$(objects))'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
foo.o bar.o
onlylove@ubuntu:~/My/makefile/05$
sort 按词法顺序排列 list 中的单词,删除重复单词。输出是由单个空格分隔的单词组成的 list。因此,
$(sort foo bar lose)
返回值 ‘bar foo lose’。
顺便说一句,由于排序会删除重复的单词,因此即使您不关心排序顺序,也可以将其用于此目的。
makefile 文件内容:
all:
@echo '$(sort foo bar lose bar)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
bar foo lose
onlylove@ubuntu:~/My/makefile/05$
返回 text 的第 n 个 word。n 的合法值从 1 开始。如果 n 大于 text 的单词数,则该值为空。例如:
$(word 2, foo bar baz)
返回 ‘bar’。
makefile 内容如下:
all:
@echo \"'$(word 2, foo bar baz)'\"
@echo \"'$(word 4, foo bar baz)'\"
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
"bar"
""
onlylove@ubuntu:~/My/makefile/05$
返回 text 中以单词 s 开头并以单词 e 结尾的单词列表(包括)。s 的合法值从 1 开始;e 可以从 0 开始。如果 s 大于 text 中的单词数,则该值为空。如果 e 大于 text 中的单词数,则返回 text 末尾的单词数。如果 s 大于 e,则不返回任何内容。例如:
$(wordlist 2, 3, foo bar baz)
返回 ‘bar baz’。
makefile 文件内容如下:
all:
@echo \"'$(wordlist 2, 3, foo bar baz)'\"
@echo \"'$(wordlist 2, 4, foo bar baz)'\"
@echo \"'$(wordlist 4, 3, foo bar baz)'\"
@echo \"'$(wordlist 4, 5, foo bar baz)'\"
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
"bar baz"
"bar baz"
""
""
onlylove@ubuntu:~/My/makefile/05$
返回 text 中 words 的个数。因此,text 的最后一个单词是 $(word $(words text),text)
。
makefile 文件内容:
text = bar foo bar baz bar
all:
@echo \"'$(words $(text))'\"
测试:
text = bar foo bar baz bar
all:
@echo \"'$(words $(text))'\"
参数 names 被认为是一系列的 names,用空格隔开。该值是该序列中的第一个名称。其余的 names 被忽略。例如:
$(firstword foo bar)
生成结果"foo"。虽然 $(firstword text)
与 $(word 1,text)
相同,但保留了 firstword 函数,因为它的简单性。
makefile 文件内容:
text = bar foo bar baz bar
all:
@echo '$(firstword $(text))'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
bar
onlylove@ubuntu:~/My/makefile/05$
参数 names 被认为是一系列的 names,用空格隔开。该值是序列中的最后一个名称。例如:
$(lastword foo bar)
生成结果 ‘bar’。虽然 $(lastword text)
和 $(words text),text)
是一样的,但是为了简单和更好的性能,我们增加了lastword函数。
makefile 文件内容如下:
text = bar foo bar baz abc
all:
@echo '$(lastword $(text))'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
abc
onlylove@ubuntu:~/My/makefile/05$
一些内置的扩展函数专门用于分解文件名或文件名列表。
下面的每个函数都对文件名执行特定的转换。函数的参数被视为一系列用空格分隔的文件名(前面和后面的空格将被忽略)。该系列中的每个文件名都以相同的方式转换,结果在它们之间用单个空格连接起来。
提取 names 中每个文件名的目录部分。文件名的目录部分包括其中最后一个斜杠以外的所有内容。如果文件名不包含斜杠,则目录部分是字符串"./"。例如:
$(dir src/foo.c hacks)
产生结果 ‘src/ ./’。
makefile 文件内容如下:
all:
@echo '$(dir src/foo.c hacks)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
src/ ./
onlylove@ubuntu:~/My/makefile/05$
提取 names 中每个文件名的目录部分以外的所有内容。如果文件名中不包含斜杠,则不做修改。否则,通过最后一个斜杠的所有内容都会被删除。
以斜杠结尾的文件名变成空字符串。这是不幸的,因为这意味着结果中以空格分隔的文件 names 的数量并不总是与参数相同;但我们没有看到任何其他有效的替代方案。例如:
$(notdir src/foo.c hacks)
产生结果 ‘foo.c hacks’。
makefile 文件内容:
all:
@echo '$(notdir src/foo.c hacks /)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
foo.c hacks
onlylove@ubuntu:~/My/makefile/05$
提取 names 中每个文件名的后缀。如果文件名包含句号,则后缀为以最后一个句号开头的所有内容。否则,后缀为空字符串。这通常意味着,当 names 不为空时,结果将为空,如果 names 包含多个文件 names,结果可能包含较少的文件 names。例如:
$(suffix src/foo.c src-1.0/bar.c hacks)
产生结果 ‘.c .c’。
makefile 文件内容:
all:
@echo '$(suffix src/foo.c src-1.0/bar.c hacks a.c.o)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
.c .c .o
onlylove@ubuntu:~/My/makefile/05$
提取 names 中每个文件名的后缀以外的所有内容。如果文件名包含句点,则 basename 是从(不包括)最后一个句点开始的所有内容。目录部分中的句点将被忽略。如果没有句点,则 basename 是整个文件名。例如:
$(basename src/foo.c src-1.0/bar hacks)
产生结果 ‘src/foo src-1.0/bar hacks’。
makefile 文件内容:
all:
@echo '$(basename src/foo.c src-1.0/bar.c hacks a.c.o)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
src/foo src-1.0/bar hacks a.c
onlylove@ubuntu:~/My/makefile/05$
参数 names 被认为是一系列的 names,用空格隔开;以 suffix 为单位。suffix 的值被附加到每个单独名称的末尾,产生的更大的名称在它们之间用单个空格连接起来。例如:
$(addsuffix .c,foo bar)
产生结果 ‘foo.c bar.c’。
makefile 文件内容:
all:
@echo '$(addsuffix .c,foo bar)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
foo.c bar.c
onlylove@ubuntu:~/My/makefile/05$
参数 names 被认为是一系列的 names,用空格隔开;以 prefix 为单位。 prefix 的值被放在每个单独名称的前面,产生的更大的名称之间用单个空格连接起来。例如:
$(addprefix src/,foo bar)
产生结果 ‘src/foo src/bar’。
makefile 文件内容如下:
all:
@echo '$(addprefix src/,foo bar)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
src/foo src/bar
onlylove@ubuntu:~/My/makefile/05$
逐字连接两个参数:连接的第一个单词(来自每个参数)构成结果的第一个单词,第二个单词构成结果的第二个单词,依此类推。所以结果的第n个单词来自于每个参数的第n个单词。如果一个参数的单词比另一个多,那么多余的单词将被复制到结果中。
例如:’$(join a b,.c .o)
’ 产生结果 ‘a.c b.o
’。
列表中单词之间的空格不保留;它被替换成一个空格。
这个函数可以合并 dir 和 notdir 函数的结果,以生成给这两个函数的原始文件列表。
makefile 文件内容:
all:
@echo '$(join a b,.c .o)'
@echo '$(join a b c,.c .o)'
@echo '$(join a b,.c .o .d)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
a.c b.o
a.c b.o c
a.c b.o .d
onlylove@ubuntu:~/My/makefile/05$
参数 pattern 是一个文件名 pattern,通常包含通配符(在shell文件名模式中)。wildcard 的结果是一个空格分隔的列表,包含与 pattern 匹配的现有文件的名称。
特别说明:在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN…) 。
makefile 文件内容:
all:
@echo '$(wildcard *.c)' # 获取当前目录下.c结尾的文件
@echo '$(wildcard ./*.c)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
a.c b.c c.c makefile
onlylove@ubuntu:~/My/makefile/05$ make
a.c c.c b.c
./a.c ./c.c ./b.c
onlylove@ubuntu:~/My/makefile/05$
对于 names 中的每个文件名,返回规范的绝对路径。规范名不包含任何 . 或 … 组件,也不包含任何重复路径分隔符(/)或符号链接。如果失败,则返回空字符串。
特别说明:makefile 里的 realpath 是返回文件的绝对地址,某些场合下可以使用 $(shell pwd) 来替代。如果目录不存在,则返回空字符串。
makefile 文件内容:
all:
@echo '$(realpath ./)'
@echo '$(shell pwd)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
/home/onlylove/My/makefile/05
/home/onlylove/My/makefile/05
onlylove@ubuntu:~/My/makefile/05$
对于 names 中的每个文件名,都返回一个不包含任何 . 或 … 组件的绝对路径,也不包含任何重复的路径分隔符(/)。注意,与realpath函数相比,abspath 不解析符号链接,也不要求文件 names 引用现有的文件或目录。使用通配符函数来测试是否存在。
特别说明:makefile 里的 abspath 是返回文件的绝对地址。如果目录不存在,也返回绝对地址。
makefile 文件内容如下:
all:
@echo '$(abspath ./temp)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls -a
. .. makefile
onlylove@ubuntu:~/My/makefile/05$ make
/home/onlylove/My/makefile/05/temp
onlylove@ubuntu:~/My/makefile/05$
有三个函数提供条件扩展。这些函数的一个关键方面是,并不是所有的实参都在一开始展开。只有那些需要展开,才会展开。
if 函数在函数上下文中提供了条件展开的支持(而GNU使 makefile 条件,如ifeq)。
第一个参数 condition 首先剥离前面和后面的所有空格,然后展开。如果扩展为任何非空字符串,则认为 condition 为真。如果展开为空字符串,则 condition 被认为是假的。
如果 condition 为真,那么第二个参数 then-part 将被求值,它将作为整个If函数求值的结果。
如果 condition 为假,则对第三个参数 else-part 求值,这是 If 函数的结果。如果没有第三个实参,则If函数的计算结果为nothing(空字符串)。
请注意,只有一个 then-part 或 else-part 将被求值,而不是两个都被求值。因此,两者都可能包含副作用(例如shell函数调用等)。
makefile 文件内容:
all:
@echo '$(if 1,'then-part','else-part')'
@echo '$(if ,'then-part','else-part')'
@echo '$(if 0,'then-part','else-part')'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
then-part
else-part
then-part
onlylove@ubuntu:~/My/makefile/05$
or 函数提供"短路"OR 操作。每个参数按顺序展开。如果参数展开为非空字符串,则处理停止,展开的结果就是该字符串。如果,在所有参数展开后,所有参数都为假(空),那么展开的结果是空字符串。
makefile 文件内容:
all:
@echo '$(or 1,'then-part','else-part')'
@echo '$(or ,'then-part','else-part')'
@echo '$(or , ,'else-part')'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
1
then-part
else-part
onlylove@ubuntu:~/My/makefile/05$
and 函数提供短路 AND 操作。每个参数按顺序展开。如果参数扩展为空字符串,则处理将停止,并且扩展的结果是空字符串。如果所有参数展开为非空字符串,则展开的结果是最后一个参数的展开。
makefile 文件内容:
all:
@echo '$(and 1,'then-part','else-part')'
@echo '$(and 'then-part','else-part',)'
@echo '$(and , ,'else-part')'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
else-part
onlylove@ubuntu:~/My/makefile/05$
foreach 函数与其他函数有很大不同。它导致 text 的一个部分被重复使用,每次对它执行不同的替换。它类似于 shell sh 中的 for 命令和 C-shell csh 中的 foreach 命令。
foreach 函数的语法为:
$(foreach var,list,text)
前两个参数 var 和 list 在执行其他操作之前展开;请注意,最后一个参数 text 不会同时展开。然后对于 list 的展开值中的每个单词,将 var 的展开值命名的变量设置为该单词,并展开 text。假设 text 包含对该变量的引用,因此每次展开都不同。
结果是,text 的扩展次数与 list 中存在空格分隔的单词一样多。将 text 的多次展开串接起来,它们之间有空格,从而得到 foreach 的结果。
这个简单的示例将变量files设置为列表dirs中目录中所有文件的列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
此处的 text 是 ‘$(wildcard $(dir)/*)
’。第一次重复找到 dir 的值 ‘a’,因此它产生与 ‘$(wildcard a/*)
’ 相同的结果;第二次重复产生了 ‘$(wildcard b/*)
的结果;第三次产生 ‘$(wildcard c/*)
’ 结果。
此示例具有与以下示例相同的结果(除了设置"dirs"):
files := $(wildcard a/* b/* c/* d/*)
当文本很复杂时,您可以通过为其命名来提高可读性,并附加一个变量:
find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))
这里我们以这种方式使用变量 find_files。我们使用普通的 ‘=’ 来定义一个递归扩展变量,以便其值包含一个实际的函数调用,以便在foreach的控制下重新扩展;简单扩展的变量不起作用,因为在定义 find_files 时只调用通配符一次。
foreach函数对变量var没有永久的影响;在foreach函数调用之后,它的值和风格与之前相同。从 list 中获取的其他值仅在foreach执行期间暂时有效。变量 var 是foreach执行期间的一个简单扩展变量。如果 var 在foreach函数调用之前未定义,那么它在调用之后也未定义。
在使用会产生变量名的复杂变量表达式时,您必须小心,因为许多奇怪的东西都是有效的变量名,但可能不是您想要的。例如:
files := $(foreach Esta-escrito-en-espanol!,b c ch,$(find_files))
如果 find_files 的值引用了名称为 ‘Esta-escrito-en-espanol!’ 的变量,则可能很有用,但它更有可能是错误的。
foreach 函数****是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell (/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。语法如下:
$(foreach var,list,text)
这个函数的意思是,把参数 list 中的单词逐一取出放到参数 var 所指定的变量中,然后再执行 text 所包含的表达式。
makefile 文件内容:
list = a b c d e f
all:
@echo $(foreach var,$(list),$(var).123)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
a.123 b.123 c.123 d.123 e.123 f.123
onlylove@ubuntu:~/My/makefile/05$
file 函数允许 makefile 对文件进行写入或读取。支持两种写入模式:覆盖,即文本被写入文件的开头,任何现有内容都会丢失;追加,即文本被写入文件的结尾,保留现有内容。在这两种情况下,如果文件不存在,则创建该文件。如果文件无法打开进行写操作,或者写操作失败,这是一个致命错误。当写入文件时,file 函数展开为空字符串。
当读取一个文件时,file 函数展开为文件的逐字内容,除了最后的换行符(如果有的话)将被剥离。试图从不存在的文件读取将展开为空字符串。
file 函数的语法是:
$(file op filename[,text])
当 file 函数被求值时,它的所有参数首先被展开,然后 filename 所指示的文件将以 op 所描述的模式打开。
操作符 op 可以是 > 表示文件将被新内容覆盖,>> 表示文件的当前内容将被追加,或 < 表示文件的内容将被读入。filename 指定要写入或读取的文件。操作符和文件名之间可以有空格。
读取文件时,提供 text 值是错误的。
当写入文件时,text 会被写入文件。如果 text 没有以换行符结尾,则将写入最后一个换行符(即使 text 是空字符串)。如果 text 参数根本没有给出,那么什么也不会写出来。
例如,如果构建系统的命令行大小有限,并且配方运行的命令也可以接受来自文件的参数,那么 file 函数就很有用。许多命令使用以 @ 为前缀的参数指定包含更多参数的文件的约定。然后你可以这样写你的配方:
program: $(OBJECTS)
$(file >[email protected],$^)
$(CMD) $(CMDFLAGS) @[email protected]
@rm [email protected]
如果命令要求每个参数都在输入文件的单独一行中,您可以这样编写配方:
program: $(OBJECTS)
$(file >[email protected]) $(foreach O,$^,$(file >>[email protected],$O))
$(CMD) $(CMDFLAGS) @[email protected]
@rm [email protected]
makefile 文件内容:
all1:
$(file > file.txt,'asdfghjkl1234567890')
all2:
$(file > file.txt,'0123456789')
all3:
@echo $(file < file.txt)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make all1
make: 'all1' is up to date.
onlylove@ubuntu:~/My/makefile/05$ ls
file.txt makefile
onlylove@ubuntu:~/My/makefile/05$ cat file.txt
'asdfghjkl1234567890'
onlylove@ubuntu:~/My/makefile/05$ make all2
make: 'all2' is up to date.
onlylove@ubuntu:~/My/makefile/05$ ls
file.txt makefile
onlylove@ubuntu:~/My/makefile/05$ cat file.txt
'0123456789'
onlylove@ubuntu:~/My/makefile/05$ make all3
0123456789
onlylove@ubuntu:~/My/makefile/05$
call 函数的独特之处在于,它可以用来创建新的参数化函数。您可以编写一个复杂的表达式作为 variable 的值,然后使用 call 来用不同的值展开它。
调用函数的语法为:
$(call variable,param,param,…)
当 make 扩展此函数时,它会将每个参数分配给临时变量 $(1)
、$(2
)等。变量$(0)
就是 variable 变量。parameter 参数没有最大数量;也没有最小值,但是使用没有参数的调用是没有意义的。
然后,在这些临时赋值的上下文中,将 variable 扩展为 make 变量。因此,在 variable 值中对 $(1)
的任何引用都将解析为调用中的第一个参数。
注意,variable 是变量的名称,而不是对该变量的引用。因此,在编写时通常不会使用 ‘$
’ 或圆括号(但是,如果希望名称不是常量,可以在名称中使用变量引用)。
如果 variable 是内置函数的名称,则总是调用内置函数(即使该名称的 make 变量也存在)。
call 函数在将 param 参数赋值给临时变量之前展开它们。这意味着,包含对具有特殊扩展规则(如foreach或if)的内置函数的引用的 variable 值,可能不能像您预期的那样工作。
一些例子可以使这一点更清楚。
这个宏只是颠倒它的参数:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
这里 foo 将包含"b a"。
这个更有趣一点:它定义了一个宏来在PATH中搜索程序的第一个实例。
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
LS := $(call pathsearch,ls)
现在,变量 LS 包含 /bin/ls 或类似内容。
函数 call 可以嵌套。每次递归调用都得到$(1)等自己的本地值。掩盖了更高级别 call 的值。例如,下面是 map 函数的实现:
map = $(foreach a,$(2),$(call $(1),$(a)))
现在,您可以在一步中将通常只接受一个参数(如origin)的函数映射为多个值:
o = $(call map,origin,o map MAKE)
最后 o 包含 ‘file file default’ 之类的东西。
最后一个警告:在 call 的参数中添加空格时要小心。与其他函数一样,保留第二个参数和后续参数中包含的任何空格;这可能会导致奇怪的效果。在向 call 提供参数时,通常最安全的做法是删除所有多余的空格。
**call 函数用来创建新的参数化函数。**语法格式如下:
$(call variable,param1,param2,…)
$(1) 表示 param1。
$(2) 表示 param2。
variable 声明 param1 、param2、……的关系。
makefile 文件内容:
variable = $(4) $(2)-$(1)$(3)
all:
@echo '$(call variable,a,b,c,d)'
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
d b-ac
onlylove@ubuntu:~/My/makefile/05$
函数 value 提供了一种使用变量值而不将其展开的方法。请注意,这不会撤消已经发生的扩展;例如,如果您创建一个简单展开的变量,其值在定义过程中会展开;在这种情况下,value 函数将返回与直接使用变量相同的结果。
value 函数的语法为:
$(value variable)
注意,variable 是变量的名称,而不是对该变量的引用。因此,在编写时通常不会使用 ‘$’ 或括号。但是,如果希望名称不是常量,可以在名称中使用 variable 引用。
这个函数的结果是一个包含变量值的字符串,没有任何展开。例如,在这个 makefile 中:
FOO = $PATH
all:
@echo $(FOO)
@echo $(value FOO)
第一个输出行将是 ATH,因为 “$P” 将扩展为 make 变量,而第二个输出行将是 $PATH 环境变量的当前值,因为值函数避免了扩展。
value 函数最常与 eval 函数一起使用。
eval 函数非常特殊:它允许您定义非常量的新makefile构造;这是对其他变量和函数求值的结果。eval 函数的参数被展开,然后展开的结果被解析为makefile语法。扩展的结果可以定义新的生成变量、目标、隐式或显式规则等。
函数 eval 的结果总是空字符串因此,它几乎可以放置在makefile中的任何位置,而不会引起语法错误。
重要的是要认识到 eval 参数被扩展了两次;首先通过 eval 函数,然后当它们被解析为makefile语法时,展开的结果再次展开。这意味着在使用 eval 时,可能需要为 ‘$’ 字符提供额外的转义级别。在这些情况下,value 函数有时可以用来避免不必要的展开。
这里有一个 eval 如何使用的例子;这个示例结合了许多概念和其他函数。尽管在这个例子中使用 eval 而不是仅仅写出规则看起来可能过于复杂,但考虑两件事:首先,模板定义(在 PROGRAM_template 中)可能需要比这里复杂得多;其次,您可以将本示例中复杂的通用部分放入另一个 makefile 中,然后将其包含在所有单个 makefile 中。现在,您的单个 makefile 非常简单。
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Everything after this is generic
.PHONY: all
all: $(PROGRAMS)
define PROGRAM_template =
$(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $@
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
eval 函数非常特殊:它允许您定义非常量的新makefile构造;这是对其他变量和函数求值的结果。函数原型如下:
$(eval text)
origin 函数不同于大多数其他函数,因为它不对变量的值进行操作;它告诉你关于 variable 的一些事情。具体来说,它告诉你它来自哪里。
origin 函数的语法为:
$(origin variable)
注意,variable 是要查询的变量的名称,而不是对该变量的引用。因此,在编写时通常不会使用 ‘$’ 或圆括号(但是,如果希望名称不是常量,可以在名称中使用 variable 引用)。
此函数的结果是一个字符串,告诉您变量变量是如何定义的:
undefined:如果变量从未定义过。
default:如果变量有一个默认的定义,就像CC一样,等等。注意,如果重新定义了一个默认变量,a函数将返回后面定义的原点。
environment:如果 variable 从环境中继承而来。
environment override:如果 variable 是从提供的环境继承的,并且由于"-e"选项而覆盖了 makefile 中 variable 的设置。
file:如果 variable 是在makefile中定义的。
command line:如果 variable 是在命令行上定义的。
override:如果 variable 在makefile中使用覆盖指令定义的。
automatic:如果 variable 是为每个规则执行配方而定义的自动变量。
此信息主要用于确定您是否要相信变量的值(除了出于您的好奇心之外)。如,假设您有一个makefile foo,其中包含另一个makefile bar。如果运行命令 ‘make -f bar’,您希望在 bar 中定义变量 bletch,即使环境包含 bletch 的定义。但是,如果 foo 在包含 bar 之前定义了 bletch,您就不希望重写该定义。这可以通过在 foo 中使用 override 指令来实现,使该定义优先于 bar 中后面的定义;不幸的是,override 指令也会覆盖任何命令行定义。因此,bar 可以包括:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
如果 bletch 已经从环境中定义,这将重新定义它。
如果你想重写 bletch 之前的定义,如果它来自于环境,即使在-e下,你也可以写:
ifneq "$(findstring environment,$(origin bletch))" ""
bletch = barf, gag, etc.
endif
这里,如果 ‘$(origin bletch)’ 返回 ‘environment’ 或 ‘environment override’,就会发生重新定义。
makefile 文件内容:
all:
@echo $(origin V)
@echo $(origin USER)
@echo $(origin CC)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
undefined
environment
default
onlylove@ubuntu:~/My/makefile/05$
与 origin 函数一样,flavor 函数不对变量的值进行操作,而是告诉您有关 variable 的一些信息。具体来说,它告诉你 variable 的属性。
flavor 函数的语法为:
$(flavor variable)
注意,variable 是要查询的变量的名称,而不是对该变量的引用。因此,在编写时通常不会使用$或圆括号(但是,如果希望名称不是常量,可以在名称中使用变量引用)。
这个函数的结果是一个字符串,它标识 variable 变量的 flavor:
undefined:如果 variable 从未定义过。
recursive:如果 variable 是递归展开的变量。
simple:如果 variable 是一个简单展开的变量。
makefile 文件内容:
all:
@echo $(flavor V)
@echo $(flavor USER)
@echo $(flavor CC)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
undefined
recursive
recursive
onlylove@ubuntu:~/My/makefile/05$
这些函数控制 make 的运行方式。通常,它们用于向 makefile 的用户提供信息,或者在检测到某种环境错误时导致 make 停止。
生成一个致命的 error,消息是 text。请注意,每当这个函数被求值时,都会生成 error。所以,如果你把它放在配方中或递归变量赋值的右边,它将不会被求值,直到以后。text 会在 error 生成之前扩展。例如:
ifdef ERROR1
$(error error is $(ERROR1))
endif
如果定义了 make 变量 ERROR1,则在读取 makefile 期间将生成致命错误。或:
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)
如果 err 目标被调用,将在 make 运行时生成一个致命错误。
makefile 文件内容:
all:
$(error err1234567890)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
makefile:3: *** err1234567890. Stop.
onlylove@ubuntu:~/My/makefile/05$
此函数的工作方式与上面的 error 函数类似,只是 make 不会退出。相反,text 被展开并显示结果消息,但 makefile 的处理仍在继续。展开这个函数的结果是空字符串。
makefile 文件内容:
all:
$(warning warning1234567890)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
makefile:3: warning1234567890
make: 'all' is up to date.
onlylove@ubuntu:~/My/makefile/05$
这个函数只不过是将其(扩展的)参数打印到标准输出。不添加makefile名称或行号。此函数扩展的结果是空字符串。
makefile 文件内容:
all:
$(info info1234567890)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
info1234567890
make: 'all' is up to date.
onlylove@ubuntu:~/My/makefile/05$
shell 函数与除 wildcard 函数以外的任何其他函数不同,因为它与 make 之外的世界进行通信。
shell 函数执行的功能与反向引号 (’’’) 在大多数 shell 中执行的功能相同:它执行命令扩展。这意味着它接受一个shell命令作为参数,并计算该命令的输出。对结果的唯一处理方法是将每个换行符(或回车符/换行符对)转换为单个空格。如果有一个尾随(回车和)换行符,它将被简单地删除。
通过调用 shell 函数运行的命令在展开函数调用时运行。因为这个函数涉及生成一个新shell,所以应该仔细考虑在递归扩展变量 vs. 中使用shell函数的性能影响。
在shell函数或 ‘!=’ 赋值操作符被使用之后,它的退出状态被放在 .SHELLSTATUS 变量中。
下面是一些使用shell函数的例子:
contents := $(shell cat foo)
将 contents 设置为 foo 文件的内容,并使用空格(而不是换行符)分隔每行。
files := $(shell echo *.c)
将files设置为 ‘*.c
’ 的扩展项。除非 make 使用一个非常奇怪的 shell,否则这与 ‘$(wildcard *.c)
’ 的结果相同(只要至少存在一个 ‘.c
’ 文件)。
makefile 文件内容:
all:
@echo $(shell pwd)
测试:
onlylove@ubuntu:~/My/makefile/05$ ls
makefile
onlylove@ubuntu:~/My/makefile/05$ make
/home/onlylove/My/makefile/05
onlylove@ubuntu:~/My/makefile/05$
如果 GNU make 是在支持 GNU Guile 作为嵌入式扩展语言的情况下构建的,那么 guile 函数将可用。guile 函数采用一个参数,该参数首先以正常方式通过 make 展开,然后传递给 GNU Guile 评估器。评估器的结果将转换为字符串,并用作 makefile 中 guile 函数的扩展。
您可以通过检查单词Guile的 .FEATURES 变量来确定是否支持GNU Guile。