十二、Makefile常用字符串处理函数
学习使用函数之前,先来看一下函数的语法结构。函数的调用和变量的调用很像。引用变量的格式为$(变量名)
,函数调用的格式如下:
$(<function> <arguments>) 或者是 ${<function> <arguments>}
其中,function 是函数名,arguments 是函数的参数,参数之间要用逗号分隔开。而参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号也可以使用花括号。这个其实我们并不陌生,我们之前使用过许多的函数,比如说展开通配符的函数 wildcard,以及字符串替换的函数 patsubst ,Makefile 中函数并不是很多。
今天主要讲的是字符串处理函数,这些都是我们经常使用到的函数,下面是对函数详细的介绍。
1. 模式字符串替换函数,函数使用格式如下:
$(patsubst <pattern>,<replacement>,<text>)
函数说明:函数功能是查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。返回值为替换后的新字符串。实例:
- OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到的值是 "1.o 2.o 3.o",这些都是替换后的值。
2. 字符串替换函数,函数使用格式如下:
$(subst <from>,<to>,<text>)
函数说明:函数的功能是把字符串中的 form 替换成 to,返回值为替换后的新字符串。实例:
- OBJ=$(subst ee,EE,feet on the street)
- all:
- @echo $(OBJ)
执行 make 命令,我们得到的值是“fEEt on the strEEt”。
3. 去空格函数,函数使用格式如下:
$(strip <string>)
函数说明:函数的功能是去掉字符串的开头和结尾的字符串,并且将其中的多个连续的空格合并成为一个空格。返回值为去掉空格后的字符串。实例:
- OBJ=$(strip a b c)
- all:
- @echo $(OBJ)
执行完 make 之后,结果是“a b c”。这个只是除去开头和结尾的空格字符,并且将字符串中的空格合并成为一个空格。
4. 查找字符串函数,函数使用格式如下:
$(findstring <find>,<in>)
函数说明:函数的功能是查找 in 中的 find ,如果我们查找的目标字符串存在。返回值为目标字符串,如果不存在就返回空。实例:
- OBJ=$(findstring a,a b c)
- all:
- @echo $(OBJ)
执行 make 命令,得到的返回的结果就是 "a"。
5. 过滤函数,函数使用格式如下:
$(filter <pattern>,<text>)
函数说明:函数的功能是过滤出 text 中符合模式 pattern 的字符串,可以有多个 pattern 。返回值为过滤后的字符串。实例:
- OBJ=$(filter %.c %.o,1.c 2.o 3.s)
- all:
- @echo $(OBJ)
执行 make 命令,我们得到的值是“1.c 2.o”。
6. 反过滤函数,函数使用格式如下:
$(filter-out <pattern>,<text>)
函数说明:函数的功能是功能和 filter 函数正好相反,但是用法相同。去除符合模式 pattern 的字符串,保留符合的字符串。返回值是保留的字符串。实例:
- OBJ=$(filter-out 1.c 2.o ,1.o 2.c 3.s)
- all:
- @echo $(OBJ)
执行 make 命令,打印的结果是“3.s”。
7. 排序函数,函数使用格式如下:
$(sort <list>)
函数说明:函数的功能是将 <list>
中的单词排序(升序)。返回值为排列后的字符串。实例:
- OBJ=$(sort foo bar foo lost)
- all:
- @echo $(OBJ)
执行 make 命令,我们得到的值是“bar foo lost”。
注意:sort会去除重复的字符串。
8. 取单词函数,函数使用格式如下:
$(word <n>,<text>)
函数说明:函数的功能是取出函数<text>
中的第n个单词。返回值为我们取出的第 n 个单词。实例:
- OBJ=$(word 2,1.c 2.c 3.c)
- all:
- @echo $(OBJ)
执行 make 命令,我们得到的值是“2.c”。
十三、Makefile常用文件名操作函数
我们在编写 Makefile 的时候,很多情况下需要对文件名进行操作。例如获取文件的路径,去除文件的路径,取出文件前缀或后缀等等。当遇到这样的问题的时手动修改是不太可能的,因为文件可能会很多,而且 Makefile 中操作文件名可能不止一次。所以 Makefile 给我们提供了相应的函数去实现文件名的操作。
注意:下面的每个函数的参数字符串都会被当作或是一个系列的文件名来看待。
1. 取目录函数,函数使用格式如下:
$(dir <names>)
函数说明:函数的功能是从文件名序列 names 中取出目录部分,如果没有 names 中没有 "/" ,取出的值为 "./" 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回“./”。实例:
- OBJ=$(dir src/foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到的值是“src/ ./”。提取文件 foo.c 的路径是 "/src" 和文件 hacks 的路径 "./"。
2. 取文件函数,函数使用格式如下:
$(notdir <names>)
函数说明:函数的功能是从文件名序列 names 中取出非目录的部分。非目录的部分是最后一个反斜杠之后的部分。返回值为文件非目录的部分。实例:
- OBJ=$(notdir src/foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到的值是“foo.c hacks”。
3. 取后缀名函数,函数使用格式如下:
$(suffix <names>)
函数说明:函数的功能是从文件名序列中 names 中取出各个文件的后缀名。返回值为文件名序列 names 中的后缀序列,如果文件没有后缀名,则返回空字符串。实例
- OBJ=$(suffix src/foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 命令,我们得到的值是“.c ”。文件 "hacks" 没有后缀名,所以返回的是空值。
4. 取前缀函数,函数使用格式如下:
$(basename <names>)
函数说明:函数的功能是从文件名序列 names 中取出各个文件名的前缀部分。返回值为被取出来的文件的前缀名,如果文件没有前缀名则返回空的字符串。实例:
- OBJ=$(notdir src/foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到值是“src/foo hacks”。获取的是文件的前缀名,包含文件路径的部分。
5. 添加后缀名函数,函数使用格式如下:
$(addsuffix <suffix>,<names>)
函数说明:函数的功能是把后缀 suffix 加到 names 中的每个单词后面。返回值为添加上后缀的文件名序列。实例:
- OBJ=$(addsuffix .c,src/foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 后我们可以得到“sec/foo.c.c hack.c”。我们可以看到如果文件名存在后缀名,依然会加上。
6. 添加前缀名函数,函数使用格式如下:
$(addperfix <prefix>,<names>)
函数说明:函数的功能是把前缀 prefix 加到 names 中的每个单词的前面。返回值为添加上前缀的文件名序列。实例:
- OBJ=$(addprefix src/, foo.c hacks)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到值是 "src/foo.c src/hacks" 。我们可以使用这个函数给我们的文件添加路径。
7. 链接函数,函数使用格式如下:
$(join <list1>,<list2>)
函数说明:函数功能是把 list2 中的单词对应的拼接到 list1 的后面。如果 list1 的单词要比 list2的多,那么,list1 中多出来的单词将保持原样,如果 list1 中的单词要比 list2 中的单词少,那么 list2 中多出来的单词将保持原样。返回值为拼接好的字符串。实例:
- OBJ=$(join src car,abc zxc qwe)
- all:
- @echo $(OBJ)
执行 make 命令,我们可以得到的值是“srcabc carzxc qwe”。很显然<list1>
中的文件名比<list2>
的少,所以多出来的保持不变。
8. 获取匹配模式文件名函数,命令使用格式如下:
$(wildcard PATTERN)
函数说明:函数的功能是列出当前目录下所有符合模式的 PATTERN 格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名。实例:
- OBJ=$(wildcard *.c *.h)
- all:
- @echo $(OBJ)
执行 make 命令,可以得到当前函数下所有的 ".c " 和 ".h" 结尾的文件。这个函数通常跟的通配符 "*" 连用,使用在依赖规则的描述的时候被展开(在这里我们的例子如果没有 wildcard 函数,我们的运行结果也是这样,"echo" 属于 shell 命令,在使用通配符的时通配符自动展开,我们这里只是相要说明一下这个函数在使用时,如果通过引用变量出现在规则中要被使用)。
十四、Makefile中的其它常用函数
之前学习过了 Makefile 中的字符串操作文件和文件名操作函数,我们今天再来看一下 Makefile 中的其他的函数。以下是这些函数的详细说明。
$(foreach <var>,<list>,<text>)
函数的功能是:把参数<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。每一次<text>
会返回一个字符串,循环过程中,<text>
的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以<var>
最好是一个变量名,<list>
可以是一个表达式,而<text>
中一般会只用<var>
这个参数来一次枚举<list>
中的单词。
实例:
- name:=a b c d
- files:=$(foreach n,$(names),$(n).o)
- all:
- @echo $(files)
执行 make 命令,我们得到的值是“a.o b.o c.o d.o”。
注意,foreach 中的 <var>
参数是一个临时的局部变量,foreach 函数执行完后,参数<var>
的变量将不再作用,其作用域只在 foreach 函数当中。
$(if <condition>,<then-part>)或(if<condition>,<then-part>,<else-part>)
可见,if 函数可以包含else
部分,或者是不包含,即if函数的参数可以是两个,也可以是三个。condition
参数是 if 表达式,如果其返回的是非空的字符串,那么这个表达式就相当于返回真,于是,then-part
就会被计算,否则else-part
会被计算。
而if函数的返回值是:如果condition
为真(非空字符串),那么then-part
会是整个函数的返回值。如果condition
为假(空字符串),那么else-part
将会是这个函数的返回值。此时如果else-part
没有被定义,那么整个函数返回空字串符。所以,then-part
和else-part
只会有一个被计算。
实例:
- OBJ:=foo.c
- OBJ:=$(if $(OBJ),$(OBJ),main.c)
- all:
- @echo $(OBJ)
执行 make 命令我们可以得到函数的值是 foo.c,如果变量 OBJ 的值为空的话,我们得到的 OBJ 的值就是main.c
。
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
call 函数是唯一一个可以用来创建新的参数化的函数。我们可以用来写一个非常复杂的表达式,这个表达式中,我们可以定义很多的参数,然后你可以用 call 函数来向这个表达式传递参数。
当 make 执行这个函数的时候,expression
参数中的变量$(1)、$(2)、$(3)等,会被参数parm1
,parm2
,parm3
依次取代。而expression
的返回值就是 call 函数的返回值。
实例 1:
- reverse = $(1) $(2)
- foo = $(call reverse,a,b)
- all:
- @echo $(foo)
那么,foo 的值就是“a b”。当然,参数的次序可以是自定义的,不一定是顺序的,
实例 2:
reverse = $(2) $(1) foo = $(call reverse,a,b) all: @echo $(foo)
此时的 foo 的值就是“b a”。
$(origin <variable>)
origin 函数不像其他的函数,它并不操作变量的值,它只是告诉你这个变量是哪里来的。
注意: variable 是变量的名字,不应该是引用,所以最好不要在 variable 中使用“$”字符。origin 函数会员其返回值来告诉你这个变量的“出生情况”。
下面是origin函数返回值:
- “undefined”:如果<variable>从来没有定义过,函数将返回这个值。
- “default”:如果<variable>是一个默认的定义,比如说“CC”这个变量。
- “environment”:如果<variable>是一个环境变量并且当Makefile被执行的时候,“-e”参数没有被打开。
- “file”:如果<variable>这个变量被定义在Makefile中,将会返回这个值。
- “command line”:如果<variable>这个变量是被命令执行的,将会被返回。
- “override”:如果<variable>是被override指示符重新定义的。
- “automatic”:如果<variable>是一个命令运行中的自动化变量
这些信息对于我们编写 Makefile 是非常有用的,例如假设我们有一个 Makefile ,其包含了一个定义文件Make.def
,在Make.def
中定义了一个变量bletch
,而我们的环境变量中也有一个环境变量bletch
,我们想去判断一下这个变量是不是环境变量,如果是我们就把它重定义了。如果是非环境变量,那么我们就不重新定义它。于是,我们在 Makefile 中,可以这样写:
- ifdef bletch
- ifeq "$(origin bletch)" "environment"
- bletch = barf,gag,etc
- endif
- endif
当然,使用override
关键字不就可以重新定义环境中的变量了吗,为什么需要使用这样的步骤?是的,我们用override
是可以达到这样的效果的,可是override
会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不是重新定义命令行传来的。
十五、Makefile命令的编写
通过上个章节的描述,我们已经知道了 Makefile 的规则是什么,他是由依赖关系规则和命令组成的。所使用的命令是由 shell 命令行组成,他们是一条一条执行的。多个命令之间要使用分号隔开,Makefile 中的任何命令都要以tab
键开始。多个命令行之间可以有空行和注释行,在执行规则时空行会被自动忽略。
通常系统中可能存在不同的 shell 。但是 make 处理 Makefile 过程时,如果没有明确的指定,那么对所有规则中的命令行的解析使用bin/sh
来完成。执行过程中使用的 shell 决定了规则中的命令的语法和处理机制。当使用默认的bin/sh
时,命令中出现的字符“#”到行末的内容被认为是注释。当然了“#”可以不在此行的行首,此时“#”之前的内容不会被作为注释处理。
命令回显
通常 make 在执行命令行之前会把要是执行的命令行输出到标准输出设备。我们称之为 "回显",就好像我们在 shell 环境下输入命令执行时一样。如果规则的命令行以字符“@”开始,则 make 在执行的时候就不会显示这个将要被执行的命令。典型的用法是在使用echo
命令输出一些信息时。
实例 1:
- OBJ=test main list
- all:
- @echo $(OBJ)
执行时将会得到test main list
这条输出信息,如果在执行命令之前没有字符“@”,那么make的输出将是echo test main list
。
我们在执行 make 时添加上一些参数,可以控制命令行是否输出。当使用 make 的时候机加上参数-n
或者是--just-print
,执行时只显示所要执行的命令,但不会真正的执行这个命令。只有在这种情况下 make 才会打印出所有的 make 需要执行的命令,其中包括了使用的“@”字符开始的命令。这个选项对于我们调试 Makefile 非常的有用,使用这个选项就可以按执行顺序打印出 Makefile 中所需要执行的所有命令。而 make 参数-s
或者是--slient
则是禁止所有的执行命令的显示。就好像所有的命令行都使用“@”开始一样。
命令的执行
当规则中的目标需要被重建的时候,此规则所定义的命令将会被执行,如果是多行的命令,那么每一行命令将是在一个独立的子 shell 进程中被执行。因此,多命令行之间的执行命令时是相互独立的,相互之间不存在以来。
在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 shell 命令行。因此:在一个规则的命令中命令行 “cd”改变目录不会对其后面的命令的执行产生影响。就是说之后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果达到这个目的,就不能把“cd”和其后面的命令放在两行来书写。而应该把这两个命令放在一行上用分号隔开。这样才是一个完整的 shell 命令行。
实例 2:
- foo:bar/lose
- cd bar;gobble lose >../foo
如果想把一个完整的shell命令行书写在多行上,需要使用反斜杠 (\)来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。例如上例我们也可以这样书写:
- foo:bar.lose
- cd bar; \
- gobble lose > ../foo
make 对所有规则的命令的解析使用环境变量“SHELL”所指定的那个程序。在 GNU make 中,默认的程序时 “/bin/sh”。不像其他的绝大多数变量,他们的只可以直接从同名的系统环境变量那里获得。make 的环境变量 “SHELL”没有使用环境变量的定义。因为系统环境变量“SHELL”指定的那个程序被用来作为用户和系统交互的接口程序,他对于不存在直接交互过程的 make 显然不合适。在 make 环境变量中“SHELL”会被重新赋值;他作为一个变量我们也可以在 Makefile 中明确的给它赋值,变量“SHELL“的默认值时“/bin/sh”。
并发执行命令
GNU make 支持同时执行多条命令。通常情况下,同一时刻只有一个命令在执行,下一个命令只有在当前命令结束之后才能够开始执行。不过可以通过 make 命令行选项 "-j" 或者 "--jobs" 来告诉 make 在同一时刻可以允许多条命令同时执行。
如果选项 "-j" 之后存在一个整数,其含义是告诉 make 在同一时刻可以允许同时执行的命令行的数目。这个数字被称为job slots
。当 "-j" 选项中没有出现数字的时候,那么同一时间执行的命令数目没有要求。使用默认的job solts
,值为1,表示make将串行的执行规则的命令(同一时刻只能由一条命令被执行)。
并行执行命令所带来的问题是显而易见的:
注意:如果使用 "include" 包含文件的时候,指定的文件不是文件的绝对路径或者是为当前文件下没有这个文件,make 会根据文件名会在以下几个路径中去找,首先我们在执行 make 命令的时候可以加入选项 "-I" 或 "--include-dir" 后面添加上指定的路径,如果文件存在就会被使用,如果文件不存在将会在其他的几个路径中搜索:"usr/gnu/include"、"usr/local/include" 和 "usr/include"。
如果在上面的路径没有找到 "include" 指定的文件,make 将会提示一个文件没有找到的警示提示,但是不会退出,而是继续执行 Makefile 的后续的内容。当完成读取整个 Makefile 后,make 将试图使用规则来创建通过 "include" 指定但不存在的文件。当不能创建的时候,文件将会保存退出。
使用时,通常用 "-include" 来代替 "include" 来忽略文件不存在或者是无法创建的错误提示,使用格式如下:
-include <filename>
使用方法和 "include" 的使用方法相同。
这两种方式之间的区别:
- 多个同时执行的命令的输出信息将同时被输出到终端。当出现错误时很难根据一大堆凌乱的信息来区分那条命令执行错误。
- 在同一时刻可能会存在多个命令执行的进程同时读取到标准输入,但是对于白哦准输入设备来说,在同一时刻只能存在一个进程访问它。就是说在某个时间点,make只能保证此刻正在执行的进程中的一个进程读取标准输入流。而其他的进程键的标准输入流将设置为无效。因此在此一时刻多个执行命令的进程中只有一个进程获得标准输入,而其他的需要读取标准输入流的进程由于输入流无效而导致致命的错误。
-
十六、Makefile include文件包含
这个章节主要讲的是 Makefile 中包含其他的文件。包含其他文件使用的关键字是 "include",和 C 语言包含头文件的方式相同。
当 make 读取到 "include" 关键字的时候,会暂停读取当前的 Makefile,而是去读 "include" 包含的文件,读取结束后再继读取当前的 Makefile 文件。"include" 使用的具体方式如下:include <filenames>
filenames 是 shell 支持的文件名(可以使用通配符表示的文件)。
注意:"include" 关键字所在的行首可以包含一个或者是多个的空格(读取的时候空格会被自动的忽略),但是不能使用 Tab 开始,否则会把 "include" 当作式命令来处理。包含的多个文件之间要使用空格分隔开。使用 "include" 包含进来的 Makefile 文件中,如果存在函数或者是变量的引用,它们会在包含的 Makefile 中展开。
include 通常使用在以下的场合:
- 在一个工程文件中,每一个模块都有一个独立的 Makefile 来描述它的重建规则。它们需要定义一组通用的变量定义或者是模式规则。通用的做法是将这些共同使用的变量或者模式规则定义在一个文件中,需要的时候用 "include" 包含这个文件。
- 当根据源文件自动产生依赖文件时,我们可以将自动产生的依赖关系保存在另一个文件中。然后在 Makefile 中包含这个文件。
- 使用 "include <filenames>" ,make 在处理程序的时候,文件列表中的任意一个文件不存在的时候或者是没有规则去创建这个文件的时候,make 程序将会提示错误并保存退出。
- 使用 "-include <filenames>",当包含的文件不存在或者是没有规则去创建它的时候,make 将会继续执行程序,只有真正由于不能完成终极目标重建的时候我们的程序才会提示错误保存退出。