makefile 定义了一系列的规则来指定,哪些文件需要先编译, 哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。 makefile 带来的好处就是——“自动化编译”
,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率
。make是一个命令工具,是一个解释 makefile 中指令的命令 工具,一般来说,大多数的 IDE 都有这个命令,比如:Delphi 的 make,Visual C++的 nmake,Linux 下 GNU 的 make。
GNU 的 make 工作时的执行步骤如下:
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5 步为第一个阶段,6-7 为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么, make 会把其展开在使用的位置。但 make 并不会完全马上展开,make 使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
makefile 中的变量赋值
在定义变量的值时,我们可以使用其它变量来构造变量的值,在 Makefile 中有两种方式来在用变量定义变量的值。
在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点你 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上.
第一种. =
先看第一种方式,也就是简单的使用“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:
foo = $(bar)bar = $(ugh)ugh = Huh
也就是“=”
是一种递归赋值的方式,它会递归向下去寻找定义的变量,例如上面 ugh 是在bar 下面定义的,却可以在定义之前被赋值。
使用这种方式有好处也有坏处,好处就是变量可以先使用再定义
,但
坏处就是递归定义引
发异常
,例如:
A = $(B)B = $(A)
或
CFLAGS = $(CFLAGS) -O
这会让 make 陷入无限的变量展开过程中去,当然,我们的 make 是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的 make 运行时非常慢,更糟糕的是,他会使用得两个 make 的函数“wildcard”和“shell”发生不可预知 的错误。因为你不会知道这两个函数会被调用多少次。
第二种. :=
为了避免上面的这种方法,我们可以使用 make 中的另一种用变量来定义变量的方法。这种方法使用的是“:=”
操作符,如:
x := fooy := $(x)x := later
这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这 样:
y := $(x) barx := foo
那么,y 的值是“bar”,而不是“foo bar”。
备注:还有一个值得注意的是使用“:=”赋值那么表示为恒等于,表示被赋值的变量不可再复
制其他内容。
第三种.+=
这种表示追加赋值,例如:
a = 12;a += 345
那么 a 现在就为 12345;
第三种. ?=
还有一个比较有用的操作符是“?=”,先看示例:
FOO ?= bar
其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义 过,那么这条语将什么也不做
makefile 中的注释
Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如:“\#”。最后.
还值得一提的是,在 Makefile 中的命令,必须要以[Tab]键开始,例如:
上图中,目标 MAIN 中的命令不可以与目标同列,要使用[Tab]键缩进后开始写。
makefile 中清空文本文件的规则
每个 Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”
clean:rm edit $(objects)
更为稳健的做法是:
.PHONY : cleanclean :-rm edit $(objects)
其中.PHONY 意思表示 clean 是一个“伪目标”。而在 rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean 的规则不要放在文件的开头,不然,这就会变成 make 的默认目标,一般放在 makefile 的末尾。
makefile 中引用其他 makefile 文件
在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。
include 的语法是:
include <filename>
filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符),在 include 前面可 以有一些空字符,但是绝不能是[Tab]键开始。include 和可以用一个或多个空格隔开。举个例子,你在当前目录有这样几个 Makefile:a.mk、b.mk、c.mk,还有一个文件叫 foo.make,以及一个变量$(bar),其包含了 e.mk 和 f.mk,那么下面的语句:
include foo.make *.mk $(bar)
等价于:
include foo.make a.mk b.mk c.mk e.mk f.mk
make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位。就好像 C/C++的#include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
1、如果 make 执行时,有“-I”或“--include-dir”参数,那么 make 就会在这个参数 所指定的目录下去寻找。
2、如果目录/include(一般是:/usr/local/bin 或/usr/include)存在的话, make 也会去找。如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”。 如:-include 其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼容的相关命令是 sinclude,其作用和这一个是一样的。
makefile 中自动变量和通配符
A.自动变量
make 的内置变量,make 赋予这些变量一些特殊的含义
a.$@
目标文件的完整名字
如上:$@表示的就是$(MAIN)
b.$+
所有依赖文件的名字,以空格隔开,可能包含重复的依赖文件名
c.$^
所有依赖文件的名字,以空格隔开,不包含重复的依赖文件名
d.$<
第一个依赖文件的名字
e.$?
所有时间戳比目标文件新的依赖文件(在 make 的时候,需要重新编译的文件)
备注:(隐性规则:同名的.o 依赖于同名的.c)
%.o:%.c
$(CC) -c $< -o $@ $(INC) $(LIBS)
B.通配符
a.%
表示一个任意文件名(不包含扩展名),例如%.c 表示以.c 结尾的任意 c 文件,%.o 表示以.o结尾的任意.o 文件。
b.*
匹配任意内容,例如 file/* 表示 file 文件夹中的使用东西,file/*.c 表示 file 文件夹中的使用 c 文件
makefile 中文件搜索
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉 make,让 make 在自动去找。Makefile 文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,mak 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make 就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
例如:
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜索。目录
由“冒号”分隔。
(当然,当前目录永远是最高优先搜索的地方)另一个设置文件搜索路径的方法是使用 make 的“vpath”关键字
(注意,它是全小写的),这不是变量,这是一个 make 的关键字,这和上面提到的那个 VPATH 变量很类似,但是 它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
第一种:
vpath <file> <dir>
为符合模式的 file 文件指定在 dir 目录中搜索。
例如:
vpath %.h ../headers
该语句表示,要求 make 在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件
在当前目录没有找到的话)
第二种:
vpath <file>
为清除符合模式的 file 文件的搜索目录。
例如:vpath %.h
该语句表示,清除上面去指定的目录查找的约束,只在当前目录查找
第三种:
vpath
为清除所有的指定搜索约束。
用法示例:
我们可以连续地使用 vpath 语句,以指定不同搜索策略。如果连续的 vpath 语句中出现了相同的,或是被重复了的,那么,make 会按照 vpath 语句的先后顺序来执行搜索。如:
vpath %.c foovpath % blishvpath %.c bar
其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
vpath %.c foo:barvpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
makefile 中命令显示和命令执行
命令显示规则
通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”
字符在命令行前,那么,这个命令将不被 make 显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译 XXX 模块......
当 make 执行时,会输出“正在编译 XXX 模块......”字符串,但不会输出命令,如果没有“@”,那么,make 将输出:
echo 正在编译 XXX 模块......
正在编译 XXX 模块......
如果 make 执行时,带入 make 参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么 样子的或是什么顺序的。
而 make 参数“-s”或“--slient”则是全面禁止命令的显示。
命令执行规则
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make 会一条一条的执行 其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分
号分隔这两条命令
。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的 基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用 分号分隔。如:
exec:cd /home/hchenpwd
当我们执行“make exec”时,例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,我们需要改成下面这样
exec:cd /home/hchen; pwd
makefile 中的错误命令处理
每当命令运行完后,make 会检测每个命令的返回码,如果命令返回成功,那么 make 会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么 make 就会终止执行当前规则,这将有可能终止所有规则的执行。
有些时候,命令的出错并不表示就是错误的。例如 mkdir 命令,我们一定需要建立一个目录,如果目录不存在,那么 mkdir 就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用 mkdir 的意思就是一定要有这样的一个目录,于是我们就不希望 mkdir 出错而终止规则的运行。
为了做到这一点,忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号“-” (在Tab 键之后),标记为不管命令出不出错都认为是成功的。如:
clean:-rm -f *.o
还有一个全局的办法是,给 make 加上“-i”或是“--ignore-errors”参数,那么,Makefile 中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。 还有一个要提一下的 make 的参数的是“-k”或是“--keep-going”,这个参数的意思 是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则
makefile 中嵌套执行 make
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的 Makefile,这有利于让我们的 Makefile 变得更加地简洁,而不至于把所有的东西全部写在一个 Makefile 中,这样会很难维护我们的 Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
例如,我们有一个子目录叫 subdir,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:
subsystem:cd subdir && $(MAKE)
其等价于:
subsystem:$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行 make 命令。我们把这个Makefile 叫做“总控 Makefile”,总控 Makefile 的变量可以传递到下级的Makefile 中,但是不会覆盖下层的Makefile 中所定义的变量,除非指定了“-e”参数。如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明:
export <变量>
如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:
unexport <变量>
如
export variable = value
其等价于:
variable = valueexport variable
如果你要传递所有的变量,那么,只要一个 export 就行了。后面什么也不用跟,表示传递所有的变量。
需要注意的是,有两个变量,一个是 SHELL,一个是 MAKEFLAGS,这两个变量不管你是 否 export,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make的参数信息,如果我们执行“总控 Makefile”时有 make 参数或是在上层 Makefile 中定义了这个变量,那么 MAKEFILES 变量将会是这些参数,并会传递到下层 Makefile 中,这是一个系统级的环境变量。
还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在 make 的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级 make 目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时, 我们会看到:
make: Entering directory `/home/hchen/gnu/make'.
而在完成下层 make 后离开目录时,我们会看到:
make: Leaving directory `/home/hchen/gnu/make'
makefile 中定义命令包
如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如:
define run-yaccyacc $(firstword $^)mv y.tab.c $@endef
这里,“run-yacc”是这个命令包的名字,其不要和 Makefile 中的变量重名。在 “define”和 “endef”中的两行就是命令序列。这个命令包中的第一个命令是运行 Yacc 程序,因为 Yacc 程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。使用示例:
foo.c : foo.y$(run-yacc)
我们可以看见,要使用这个命令包,我们就好像使用变量一样。
makefile 中使用条件判断
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
第一种:ifeq
ifeq (value1,value2 )
make 语句
endif
或
ifeq 'value1' 'value2'
make 语句
endif
或
ifeq "value1" "value2"
make 语句
endif
或
ifeq "value1" 'value2' 和 ifeq 'value1' "value2"
make 语句
endif
或多重分支
ifeq (value1,value2 )
make 语句 1
else
make 语句 2
endif
备注:比较参数“value1”和“value2”的值是否相同,相同为真执行。当然,参数中我们还可
以使用 make 的函数。
第二种:ifneq
语法和上面写法一样,不过它比较的是 value1 和 value2 的值是否不同,不同才为真执行。
第三种:ifdef
语法:
ifdef value
make 语句
endif
或
ifdef value
make 语句 1
else
make 语句 2
endif
如果 value 的值非空,那到表达式为真。否则表达式为假。当然,同样 value 可以是一个函数的返回值。注意,ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置。
第四种:ifndef
和 ifdef 相反
makefile 中的函数
在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。
函数的使用语法
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$( 函数名 参数) 或是 ${函数名 参数 }
参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式, 而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。
例如:
CSRSC:=$(wildcard ./c_file/*.c) #c 文件名称
上图中使用 wildcard 文件名展开函数,查找./c_file 文件夹中所有的 c 文件,如果 c_file 文件夹中有 a.c b.c 和 c.c。那么 CSRSC 的值就是 a.c b.c c.c。
makefile 中使用 shell 命令
在 makefile 中运行调用 shell 命令。语法:
$(shell 命令名称 命令参数)
例如:
$(shell pwd)
$(shell cd ../)
$(shell ls -al)
makefile 中的字符串处理函数
字符串替换函数 subst
语法:
$(subst value1,value2,str)
描述:
把字符串 str 中的 value1 替换成 value2。
返回:
函数返回被替换过后的字符串。
示例:
$(subst ee,EE,feet on the street)
把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。
模式字符串替换函数 patsubst
语法:
$(patsubst value1,value2,str)
描述:
查找 str 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 value1,如果匹配的话,则以 value2 模式替换。
返回:
函数返回被替换过后的字符串。
示例:
CSRCS += $( wildcard $( PWD )/src/ * .c)# 把 CSRCS 中的文件名 , 变为同名的 .o 文件OBJS := $( patsubst % .c, % .o,$( CSRCS ))
上面例子把$(PWD)/str 中的所有.c 文件都替换成.o 文件
去空格函数(开头和结尾)strip
语法:
$(strip str)
描述:
去掉字串 str 中开头和结尾的空字符。
返回:
返回被去掉空格的字符串值
查找字符串函数(findstring)
语法:
$(findstring str1,str2)
描述:
在字符串 str2 中查找字符串 str1
返回:
如果找到,返回 str1,没有则返回空字符串
过滤函数(filter)
语法:
$(filter patt…,str)
描述:
以 patt 模式过滤 str 字符串中的单词,保留符合 patt 模式的单词,可以有多个模式
返回:
返回符合 patt 模式的字符串
示例:
sources := foo.c bar.c baz.s ugh.hfoo: $(sources)cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。
反过滤函数(filter-out)
语法:
$(filter-out patt…,str)
描述:
以 patt 模式过滤 str 字符串中的单词,去除符合 patt 模式的单词,可以有多个模式
返回:
返回不符合 patt 模式的字符串
示例:
sources := foo.c bar.c baz.s ugh.hfoo: $(sources)cc $(filter-out %.c %.s,$(sources)) -o foo
$(filter-out %.c %.s,$(sources))返回的值是“ugh.h”。
排序函数(sort)
语法:
$(sort str)
描述:
给字符串 str 中的单词排序(升序)
返回:
返回排序后的字符串
示例:
$(sort foo bar lose)
返回“bar foo lose”
备注:
sort 函数会去掉 str 中相同的单词。
取单词函数(word)
语法:
$(word n,str)
描述:
取字符串 str 中第 n 个单词(从 1 开始)
返回:
返回字符串 str 中第 n 个单词。如果 n 比 str 中的单词总数大,则返回空字符串
取单词串函数(wordlist)
语法:
$(wordlist s,e,str)
描述:
从字符串 str 中取从 s 到 e 个的单词串
返回:
返回字符串 str 中从 s 到 e 的单词串,如果 s 比 str 中的单词数要大,则返回空字符串,如果 e大于 str 中的最大单词数,那么返回从 s 开始后面的所有单词串。
单词个数统计函数(words)
语法:
$(words str)
描述:
统计 str 中的单词数
返回:
str 中的单词数
取字符串首单词函数(firstword)
语法:
$(firstword str)
描述:
取字符串 str 中的第一个单词
返回:
返回字符串 str 中的第一个单词
makefile 中的文件操作函数
取目录函数 dir
语法:
$(dir names…)
描述:
从文件名 names 序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
返回:
返回文件名序列 names 的目录部分。
示例:
$(dir src/foo.c hacks)
返回值是“src/ ./”。
取文件函数 notdir
语法:
$(notdir names…)
描述:
从文件名 names 序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
返回:
返回文件名序列 names 的非目录部分。
示例:
$( notdir src/foo.c hacks)
返回值是“foo.c hacks”。
取后缀函数 suffix
语法:
$(suffix names…)
描述:
从文件名 names 序列中取出各个文件名的后缀
返回:
返回文件名序列 names 的后缀序列,没有后缀返回空字符串
示例:
$( suffix src/foo.c hacks)
返回值是“.c ”。
取前缀函数 basename
语法:
$(basename names…)
描述:
从文件名 names 序列中取出各个文件名的前缀部分
返回:
返回文件名序列 names 的后缀序列,如果文件没有前缀返回空字符串
示例:
$( basename src/foo.c hacks)
返回值是“foo hacks ”。
加后缀函数 addsuffix
语法:
$(addsuffix suffix,names…)
描述:
把后缀 suffix 加到 names 序列中每个单词后面
返回:
返回加过后缀的文件名序列
示例:
$(addsuffix .c, src/foo hacks)
返回值是“foo.c hacks.c ”。
加前缀函数 addprefix
语法:
$(addprefix prefix,names…)
描述:
把后缀 prefix 加到 names 序列中每个单词前面
返回:
返回加过前缀的文件名序列
示例:
$(addprefix str/, foo.c hacks)
返回值是“str/foo.c str/hacks.c ”。
连接函数 join
语法:
$(join list1,list2)
描述:
把 list2 中的单词对应的加到 list1 的单词后面,如果 list1 的单词个数要比 list2 多,那么, list1 中多出来的单词将保存原样,如果 list2 的单词数量要比 list1 多,那么 list2 多出来的单词将被复制到 list1 中
返回:
返回连接过后的字符串
示例:
$(join aaa bbb , 111 222 333)
返回值是“aaa111 bbb222 333”。
foreach 函数
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile 中的 foreach 函数几乎是仿照于 Unix 标准 Shell(/bin/sh)中的 for 语句,或是 C-Shell(/bin/csh)中的 foreach 语句而构建的。它的语法
是:
$(foreach var,list,text)
描述:
这个函数的意思是,把参数 list 中的单词逐一取出放到参数 var 所指定的变量中,然后再执
行 text 所包含的表达式。每一次 text 会返回一个字符串,循环过程中,text 的所返回的每个字
符串会以空格分隔,最后当整个循环结束时,text 所返回的每个字符串 所组成的整个字符串
(以空格分隔)将会是 foreach 函数的返回值。
所以,var 最好是一个变量名,text 可以是一个表达式,而 text 中一般会使用 var 这个参数
来依次枚举 list 中的单词。举个
例子
:
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”。
备注:
注意,foreach 中的 var 参数是一个临时的局部变量,foreach 函数执行完后,参数 var 的
变量将不在作用,其作用域只在 foreach 函数当中。
if 函数
if 函数很像 GNU 的 make 所支持的条件语句——ifeq(参见前面所述的章节),if 函数的语
法
是:
$(if con,then)
或
$(if con,then,elsethen)
描述:
可见,if 函数可以包含“else”部分,或是不含。即 if 函数的参数可以是两个,也可以是三个。参数 con 是 if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是 then 会被计算,否则 elsethen 会被计算。
返回值:
而 if 函数的返回值是,如果 con 为真(非空字符串),那个 then 会是整个函数的返回值, 如果 con 为假(空字符串),那么 elsethen 会是整个函数的返回值,如果是第一种形式且 con为假,那么,整个函数返回空字串。
call 函数
call 函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用 call 函数来向这个表达式传递参数。其语
法
是:
$(call expr,parm1,parm2,parm3...)
描述:
当 make 执行这个函数时,expr 参数中的变量,如$(1),$(2),$(3)等,会被参数 parm1,pram2,parm3 依次取代。而 expr 的返回值就是 call 函数的返回 值。例如
:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么,foo 的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的, 如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的 foo 的值就是“b a”。
origin 函数
origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你这个变量是哪里来的?
其
语法
是:
$(origin variable)
描述:
注意,variable 是变量的名字,不应该是引用。所以你最好不要在中使用“$”字符。origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是 origin 函数的返回值:
1.返回“undefined”
如果 variable 从来没有定义过,origin 函数返回这个值“undefined”。
2.返回
“default”
如果 variable 是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
3.返回
“file”
如果 variable 这个变量被定义在 Makefile 中。
4.返回“command line”
如果 variable 这个变量是被命令行定义的。
5.返回
“override”
如果 variable 是被 override 指示符重新定义的。
6.返回
“automatic”
如果 variable 是一个命令运行中的自动化变量。如$@,$+啥的
7.返回“environment“
如果 variable 是一个环境变量,并且当 Makefile 被执行时,“-e” 参数没有被打开。 这些信息对于我们编写 Makefile 是非常有用的,例如,假设我们有一个 Makefile 其包含了一个定义文件 Make.def,在 Make.def 中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把它重定义了,如果来源于 Make.def 或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile 中,我们可以这样写:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
控制 make 的函数
error 函数
语法:
$(error text…)
描述:
产生一个致命的错误,text 是错误信息。注意,error 函数不会在一被使用就会产生错误信 息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。
示例:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
在变量 ERROR_001 定义了后执行时产生 error 调用
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)
在目录 err 被执行时才发生 error 调用。
warning 函数
语法:
$(warning text…)
描述:
这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而 make继续执行。
搜索文件函数 wildcard
语法:
$(wildcard filefd)
描述:
返回 filefd 描述中的文件名,以空格隔开。
示例:
CSRSC:=$(wildcard ./c_file/*.c) #c 文件名称
CSRSC 中保存了./c_file 文件夹中的所有 c 文件
makefile 中关于命令的系统变量
AR
函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C 语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CO
从 RCS 文件中扩展文件程序。默认命令是“co”。
CPP
C 程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
FC
Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。
GET
从 SCCS 文件中扩展文件的程序。默认命令是“get”。
LEX
Lex 方法分析器程序(针对于 C 或 Ratfor)。默认命令是“lex”。
PC
Pascal 语言编译程序。默认命令是“pc”。
YACC
Yacc 文法分析器(针对于 C 程序)。默认命令是“yacc”。
YACCR
Yacc 文法分析器(针对于 Ratfor 程序)。默认命令是“yacc –r”。
MAKEINFO
转换 Texinfo 源文件(.texi)到 Info 文件程序。默认命令是“makeinfo”。
TEX
从 TeX 源文件创建 TeX DVI 文件的程序。默认命令是“tex”。
TEXI2DVI
从 Texinfo 源文件创建军 TeX DVI 文件的程序。默认命令是“texi2dvi”。
WEAVE
转换 Web 到 TeX 的程序。默认命令是“weave”。
CWEAVE
转换 C Web 到 TeX 的程序。默认命令是“cweave”。
TANGLE
转换 Web 到 Pascal 语言的程序。默认命令是“tangle”。
CTANGLE
转换 C Web 到 C。默认命令是“ctangle”。
RM
删除文件命令。默认命令是“rm –f”。