赞
踩
目录
我们先来粗略地看一看 makefile 的规则:
target ... : prerequisites ... command
target 可以是一个 object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
prerequisites 生成该 target 所依赖的文件和/或 target 。
command 该 target 要执行的命令(任意的 shell 命令)。
注意:prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。
这就是 makefile 的规则,也就是 makefile 中最核心的内容。
代码如下(示例):
- edit : main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- cc -o edit main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- main.o : main.c defs.h
- cc -c main.c
- kbd.o : kbd.c defs.h command.h
- cc -c kbd.c
- command.o : command.c defs.h command.h
- cc -c command.c
- display.o : display.c defs.h buffer.h
- cc -c display.c
- insert.o : insert.c defs.h buffer.h
- cc -c insert.c
- search.o : search.c defs.h buffer.h
- cc -c search.c
- files.o : files.c defs.h buffer.h command.h
- cc -c files.c
- utils.o : utils.c defs.h
- cc -c utils.c
- clean :
- rm edit main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
在默认的方式下,也就是我们只输入 make 命令。那么,
1、make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文 件,并把这个文件作为最终的目标文件。
3、如果 edit 文件不存在,或是 edit 所依赖的后面的 .o 文件的文件修改时间要比 edit 这个文件新, 那么,他就会执行后面所定义的命令来生成 edit 这个文件。
4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如 果找到则再根据那一个规则生成 .o 文件。(这有点像一个堆栈的过程)
5、当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,然后再用 .o 文件生成 make 的终极任务,也就是执行文件 edit 了。
注意:像 clean 这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要 make 执行。即命令——make clean ,以此来清除 所有的目标文件,以便重编译
在上面的例子中,先让我们看看 edit 的规则:
- edit : main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- cc -o edit main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
我们可以看到 .o 文件的字符串被重复了两次,当然,我们的 makefile 并不复杂,所 以在两个地方加也不累,但如果 makefile 变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而 导致编译失败。所以,为了 makefile 的易维护,在 makefile 中我们可以使用变量。makefile 的变量也就 是一个字符串,理解成 C 语言中的宏可能会更好。
比如,我们声明一个变量,叫 objects ,OBJECTS ,objs ,OBJS ,obj 或是 OBJ ,反正不管什么 啦,只要能够表示 obj 文件就行了。我们在 makefile 一开始就这样定义:
- objects = main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
然后,我们就可以很方便地在我们的 makefile 中以 $(objects) 的方式来使用这个变量了。
GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个 .o 文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。只要 make 看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中。
- objects = main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- edit : $(objects)
- cc -o edit $(objects)
- main.o : defs.h
- kbd.o : defs.h command.h
- command.o : defs.h command.h
- display.o : defs.h buffer.h
- insert.o : defs.h buffer.h
- search.o : defs.h buffer.h
- files.o : defs.h buffer.h command.h
- utils.o : defs.h
- .PHONY : clean
- clean :
- rm edit $(objects)
这种方法,也就是 make 的“隐晦规则”。上面文件内容中,.PHONY 表示 clean 是个伪目标文件。关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。
每个 Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。比较稳健的做法是:
- .PHONY : clean
- clean :
- -rm edit $(objects)
在 rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean 的规则不要放在文件的开头,不然,这就会变成 make 的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean 从来都是放在文件的最后”。
在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的 #include ,被包含的文件会原模原样的放在当前文件的包含位置。include 的语法是:
include <filename>
filename 可以是当前操作系统 Shell 的文件模式(可以包含路径和通配符)。
在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始。include 和 <filename> 可以用一 个或多个空格隔开。举个例子,你有这样几个 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、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也 会去找。
如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”。
在 Makefile 中,规则的顺序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的目标都 是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有 很多个,那么,第一个目标会成为最终的目标。make 所完成的也就是这个目标。
- targets : prerequisites ; command
- command
一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make 支持三个通配符:* ,? 和 ~ 。这是和 Unix 的 B-Shell 是相同的。
波浪号(~ )字符在文件名中也有比较特殊的用途。如果是 ~/test ,这就表示当前用户的 $HOME 目录下的 test 目录。而 ~hchen/test 则表示用户 hchen 的宿主目录下的 test 目录。(这些都是 Unix 下的小知识了,make 也支持)而在 Windows 或是 MS-DOS 下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。
通配符代替了你一系列的文件,如 *.c 表示所有后缀为 c 的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:* ,那么可以用转义字符 \ ,如 \* 来表示真实的 * 字符,而不是任意长度的字符串。
objects = *.o
上面这个例子,表示了通配符同样可以用在变量中。但是*.o 不会展开。如果你要让通配符在变量中展开,你可以这样:
objects := $(wildcard *.o)
另给一个变量使用通配符的例子:
1. 列出一确定文件夹中的所有.c文件。
objects := $(wildcard *.c)
2. 列出 (1) 中所有文件对应的 .o 文件,在(3)中我们可以看到它是由 make 自动编译出的:
$(patsubst %.c,%.o,$(wildcard *.c))
3. 由 (1)(2) 两步,可写出编译并链接所有 .c 和 .o 文件
- objects := $(patsubst %.c,%.o,$(wildcard *.c))
- foo : $(objects)
- cc -o foo $(objects)
1、Makefile 文件中的特殊变量 VPATH 。如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make 就会在当前目录找不到的情 况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
2、另一个设置文件搜索路径的方法是使用 make 的“vpath”关键字(注意,它是全小写的),它更为灵活。它可以指定不同的文件在不同的搜索目录中。它的使用方法有三种:
1、vpath <pattern> <directories> 为符合模式 <pattern> 的文件指定搜索目录 <directories>。
2、vpath <pattern> 清除符合模式 <pattern> 的文件的搜索目录。
3、vpath 清除所有已被设置好了的文件搜索目录。
vapth 使用方法中的 <pattern> 需要包含 % 字符。% 的意思是匹配零或若干字符,(需引用 % ,使 用 \ )例如,%.h 表示所有以 .h 结尾的文件。<pattern> 指定了要搜索的文件集,而 <directories> 则指定了 < pattern> 的文件集的搜索的目录。例如:
- vpath %.h ../headers
- vpath %.c foo:bar
- vpath % blish
“伪目标”并不是一个文件,只是一个标签,由于“伪目标” 不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明 一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是“伪目标”。
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为 “默认目标”,只要将其放在第一个。如果你的 Makefile 需要一口气生成若干个可执行文 件,但你只想简单地敲一个 make 完事,并且,所有的目标文件都写在一个 Makefile 中,那么你可以使用“伪目标”这个特性:
- all : prog1 prog2 prog3
- .PHONY : all
- prog1 : prog1.o utils.o
- cc -o prog1 prog1.o utils.o
- prog2 : prog2.o
- cc -o prog2 prog2.o
- prog3 : prog3.o sort.o utils.o
- cc -o prog3 prog3.o sort.o utils.o
我们知道,Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一 个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到 了我们一口气生成多个目标的目的。.PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显 式“.PHONY : all”不写的话一般情况也可以正确的执行,这样 make 可通过隐式规则推导出,“all”是一个伪目标,执行 make 不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
Makefile 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不 是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 $@ (关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
- 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
其中,-$(subst output,,$@) 中的 $ 表示执行一个 Makefile 的函数,函数名为 subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思,$@ 表示目标的集合,就像一个 数组,$@ 依次取出目标,并执于命令。
<targets ...> : <target-pattern> : <prereq-patterns ...> <commands>1、targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
2、target-pattern 是指明了 targets 的模式,也就是的目标集模式。
3、prereq-patterns 是目标的依赖模式,它对 target-pattern 形成的模式再进行一次依赖目标的定义。
看一个例子:
- 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.o bar.o”)。于是,上面的规则展开后等价于下面的规则:
- foo.o : foo.c
- $(CC) -c $(CFLAGS) foo.c -o foo.o
- bar.o : bar.c
- $(CC) -c $(CFLAGS) bar.c -o bar.o
gcc -M main.c 的输出是:
- main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
- /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
- /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
- /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
- /usr/include/bits/sched.h /usr/include/libio.h \
- /usr/include/_G_config.h /usr/include/wchar.h \
- /usr/include/bits/wchar.h /usr/include/gconv.h \
- /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
- /usr/include/bits/stdio_lim.h
gcc -MM main.c 的输出则是:
main.o: main.c defs.h
通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @ 字符在命令行前,那么,这个命令将不被 make 显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:
@echo 正在编译 XXX 模块......
当 make 执行时,会输出“正在编译 XXX 模块⋯⋯”字串,但不会输出命令,如果没有“@”,那 么,make 将输出:
echo 正在编译 XXX 模块......
正在编译 XXX 模块......
如果 make 执行时,带入 make 参数 -n 或 --just-print ,那么其只是显示命令,但不会执行令, 这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺 序的。而 make 参数 -s 或 --silent 或 --quiet 则是全面禁止命令的显示。
示例一:
- exec:
- cd /home/hchen
- pwd
示例二:
- exec:
- cd /home/hchen;pwd
当我们执行 make exec 时,第一个例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,而第二个例子中,cd 就起作用了,pwd 会打印出“/home/hchen”。
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在 每个目录中都书写一个该目录的 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 <variable ...>;
如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:
unexport <variable ...>;
如果你要传递所有的变量,那么,只要一个 export 就行了。后面什么也不用跟,表示传递所有的变量。
需要注意的是,有两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管你是否 export, 其总是要传递到下层 Makefile 中,特别是 MAKEFLAGS 变量,其中包含了 make 的参数信息,如果我们执行“总控 Makefile”时有 make 参数或是在上层 Makefile 中定义了这个变量,那么 MAKEFLAGS 变量将会是这些参数,并会传递到下层 Makefile 中,这是一个系统级的环境变量。
但是 make 命令中的有几个参数并不往下传递,它们是 -C , -f , -h, -o 和 -W (有关 Makefile 参数 的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:
- subsystem:
- cd subdir && $(MAKE) MAKEFLAGS=
如果你定义了环境变量 MAKEFLAGS ,那么你得确信其中的选项是大家都会用到的,如果其中有 -t , -n 和 -q 参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。
还有一个在“嵌套执行”中比较有用的参数,-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'
当你使用 -C 参数来指定 make 下层 Makefile 时,-w 会被自动打开的。如果参数中有 -s(--slient )或是 --no-print-directory ,那么,-w 总是失效的。
如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以 define 开始,以 endef 结束,如:
- define run-yacc
- yacc $(firstword $^)
- mv y.tab.c $@
- endef
这里,“run-yacc”是这个命令包的名字,其不要和 Makefile 中的变量重名。在 define 和 endef 中 的两行就是命令序列。这个命令包中的第一个命令是运行 Yacc 程序,因为 Yacc 程序总是生成“y.tab.c” 的文件,所以第二行的命令就是把这个文件改改名字。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的 $ 字符,那么你需要用 $$ 来表示。 变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。
在定义变量的值时,我们可以使用其它变量来构造变量的值,在 Makefile 中有两种方式来在用变量定义变量的值。
先看第一种方式,也就是简单的使用 = 号,在 = 左侧是变量,右侧是变量的值,右侧变量的值可以 定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:
- foo = $(bar)
- bar = $(ugh)
- ugh = Huh?
- all:
- echo $(foo)
但这种形式也有不好的地方,那就是递归定义,如:
- A = $(B)
- B = $(A)
这会让 make 陷入无限的变量展开过程中去,当然,我们的 make 是有能力检测这样的定义,并会 报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的 make 运行时非常慢,更糟糕的是, 他会使用得两个 make 的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。
为了避免上面的这种方法,我们可以使用 make 中的另一种用变量来定义变量的方法。这种方法使用的是 := 操作符,如:
- x := foo
- y := $(x) bar
- x := later
- 其等价于:
- y := foo bar
- x := later
值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
- y := $(x) bar
- x := foo
那么,y 的值是“bar”,而不是“foo bar”。
下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:
- nullstring :=
- space := $(nullstring) # end of the line
nullstring 是一个 Empty 变量,其中什么也没有,而我们的 space 的值是一个空格。因为在操作符 的右边是很难描述一个空格的,这里采用的技术很管用,先用一个 Empty 变量来标明变量的值开始了, 而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意 这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量:
dir := /foo/bar # directory to put the frobs in
dir 这个变量的值是“/foo/bar”,后面还跟了 4 个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。
还有一个比较有用的操作符是 ?= ,其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那就什么也不做。
FOO ?= bar
第一种是变量值的替换:我们可以替换变量中的共有的部分,其格式是 $(var:a=b) 或是${var:a=b} ,其意思是,把变量 “var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
- foo := a.o b.o c.o
- bar := $(foo:.o=.c)
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:
- foo := a.o b.o c.o
- bar := $(foo:%.o=%.c)
第二种高级用法是——“把变量的值再当成变量”。先看一个例子:在这个例子中,$(x) 的值是“y”,所以 $($(x)) 就是 $(y),于是 $(a) 的值就是“z”。
- x=y
- y=z
- a := $($(x))
在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:
- first_second = Hello
- a = first
- b = second
- all = $($a_$b)
当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:
- dir = foo
- $(dir)_sources := $(wildcard $(dir)/*.c)
- define $(dir)_print
- lpr $($(dir)_sources)
- endef
我们可以使用 += 操作符给变量追加值,如:
- objects = main.o foo.o bar.o utils.o
- objects += another.o
- 等价于:
- objects = main.o foo.o bar.o utils.o
- objects := $(objects) another.o
还有一种设置变量值的方法是使用 define 关键字。使用 define 关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。
define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以 [Tab] 键开头,所以如果你用 define 定义的命令变量中没有以 Tab 键开头,那么 make 就不会把其认为是命令。
我们可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。如:
- 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。
我们可以给定一种“模式”,可以把变量定义 在符合这种模式的所有目标上。所以,我们可以以如下方式给所有以 .o 结 尾的目标定义目标变量:
%.o : CFLAGS = -O
函数调用,很像变量的使用,也是以 $ 来标识的,其语法如下:
$(<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 <from>,<to>,<text>)
名称:字符串替换函数
功能:把字串<text>中的<from>字符串替换成<to>。返回:函数返回被替换过后的字符串。
$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern> ,如果匹配的话,则以 <replacement> 替换。这里,<pattern> 可以包括通配符 % , 表示任意长度的字串。如果 <replacement> 中也包含 % ,那么,<replacement> 中的这个 % 将是<pattern> 中的那个 % 所代表的字串。(可以用 \ 来转义,以 \% 来表示真实含义的 % 字符)
返回:函数返回被替换过后的字符串。
$(strip <string>)
名称:去空格函数。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
$(findstring <find>,<in>)
名称:查找字符串函数
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。
$(filter <pattern...>,<text>)
名称:过滤函数
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以 有多个模式。
返回:返回符合模式<pattern>的字串。
$(filter-out <pattern...>,<text>)
返回:返回不符合模式<pattern>的字串。
$(sort <list>)
名称:排序函数
功能:给字符串<list>中的单词排序(升序)。返回:返回排序后的字符串。
备注:sort函数会去掉<list>中相同的单词。
$(word <n>,<text>)
名称:取单词函数
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字 符串。
$(wordlist <ss>,<e>,<text>)
名称:取单词串函数
功能:从字符串<text>中取从<ss>开始到<e>的单词串。<ss>和<e>是一个数字。
返回:返回字符串<text>中从<ss>到<e>的单词字串。如果<ss>比<text>中的单词数要大, 那么返回空字符串。如果 <e> 大于 <text> 的单词数,那么返回从 <ss> 开始,到 <text> 结束的单词串。
$(words <text>)
名称:单词个数统计函数
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words,foo bar baz)返回值是3。
备注:如果我们要取 <text> 中最后的一个单词,我们可以这样:$(word $(words <text>), <text>) 。
$(firstword <text>)
名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstwordfoobar)返回值是foo。
备注:这个函数可以用word函数来实现:$(word1,<text>)。
$(dir <names...>)
名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(/)之前的部分。 如果没有反斜杠,那么返回 ./ 。
返回:返回文件名序列<names>的目录部分。
示例:$(dir src/foo.c hacks)返回值是src/ ./。
$(notdir <names...>)
名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例:$(notdir src/foo.c hacks)返回值是foo.c hacks。
$(suffix <names...>)
名称:取後缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是.c .c。
$(basename <names...>)
名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是src/foo src-1.0/bar hacks。$(addprefix <prefix>,<names...>)
名称:加前缀函数——addprefix。
• 功能:把前缀<prefix>加到<names>中的每个单词后面。
• 返回:返回加过前缀的文件名序列。
• 示例:$(addprefixsrc/,foobar)返回值是src/foosrc/bar。$(addsuffix <suffix>,<names...>)(加后缀)
$(join <list1>,<list2>)
名称:连接函数——join。
功能:把 <list2> 中的单词对应地加到 <list1> 的单词后面。如果 <list1> 的单词个数要比 <list2> 的多,那么,<list1> 中的多出来的单词将保持原样。如果 <list2> 的单词个数要比 <list1> 多,那么,<list2> 多出来的单词将被复制到 <list1> 中。
返回:返回连接过后的字符串。
示例:$(join aaa bbb,111 222 333)返回值是aaa111 bbb222 333。
$(foreach <var>,<list>,<text>)
把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式。每一次 <text> 会返回一个字符串,循环过程中,<text> 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
$(if <condition>,<then-part>)
或$(if <condition>,<then-part>,<else-part>)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。