当前位置:   article > 正文

MakeFile学习笔记_makefile静态模式

makefile静态模式

目录

一、makefile介绍

1.1  makefile的规则

1.2   一个事例

1.3  makefile中使用变量

1.4  让make自动推导

1.5  清空目标文件的规则

1.6  引用其他的makefile

二、书写规则

2.1  规则的语法

2.2  文件搜寻

2.3  伪目标

2.4  多目标

 2.5  静态模式

2.6  自动生成依赖性(自动找寻头文件)

三、书写命令

3.1  显示命令

3.2  命令执行

3.3  嵌套执行make

3.4  定义命令包

四、使用变量

4.1  变量中的变量

4.2 变量的高级用法

4.3 追加变量值

4.4  多行变量

4.5 目标变量

4.6 模式变量

五、使用函数

5.1 函数的调用语法

5.2 字符串处理函数

5.2.1  subst

5.2.2 patsubst

5.2.3  strip

5.2.4  findstring

5.2.5 filter

5.2.6 filter-out

5.2.7 sort

5.2.8 word

5.2.9 wordlist

5.2.10 words

5.2.11 firstword

5.3 文件名操作函数

5.4 foreach 函数和if 函数


一、makefile介绍

1.1  makefile的规则

我们先来粗略地看一看 makefile 的规则:

target ... : prerequisites ...
    command

 target 可以是一个 object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。

prerequisites 生成该 target 所依赖的文件和/或 target 。

command 该 target 要执行的命令(任意的 shell 命令)。

注意:prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。

这就是 makefile 的规则,也就是 makefile 中最核心的内容。

1.2   一个事例

代码如下(示例):

  1. edit : main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. cc -o edit main.o kbd.o command.o display.o \
  4. insert.o search.o files.o utils.o
  5. main.o : main.c defs.h
  6. cc -c main.c
  7. kbd.o : kbd.c defs.h command.h
  8. cc -c kbd.c
  9. command.o : command.c defs.h command.h
  10. cc -c command.c
  11. display.o : display.c defs.h buffer.h
  12. cc -c display.c
  13. insert.o : insert.c defs.h buffer.h
  14. cc -c insert.c
  15. search.o : search.c defs.h buffer.h
  16. cc -c search.c
  17. files.o : files.c defs.h buffer.h command.h
  18. cc -c files.c
  19. utils.o : utils.c defs.h
  20. cc -c utils.c
  21. clean :
  22. rm edit main.o kbd.o command.o display.o \
  23. 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 ,以此来清除 所有的目标文件,以便重编译

1.3  makefile中使用变量

在上面的例子中,先让我们看看 edit 的规则:

  1. edit : main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. cc -o edit main.o kbd.o command.o display.o \
  4. insert.o search.o files.o utils.o

        我们可以看到 .o 文件的字符串被重复了两次,当然,我们的 makefile 并不复杂,所 以在两个地方加也不累,但如果 makefile 变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而 导致编译失败。所以,为了 makefile 的易维护,在 makefile 中我们可以使用变量。makefile 的变量也就 是一个字符串,理解成 C 语言中的宏可能会更好。

        比如,我们声明一个变量,叫 objects ,OBJECTS ,objs ,OBJS ,obj 或是 OBJ ,反正不管什么 啦,只要能够表示 obj 文件就行了。我们在 makefile 一开始就这样定义:

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o

        然后,我们就可以很方便地在我们的 makefile 中以 $(objects) 的方式来使用这个变量了。

1.4  让make自动推导

        GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个 .o 文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。只要 make 看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. edit : $(objects)
  4. cc -o edit $(objects)
  5. main.o : defs.h
  6. kbd.o : defs.h command.h
  7. command.o : defs.h command.h
  8. display.o : defs.h buffer.h
  9. insert.o : defs.h buffer.h
  10. search.o : defs.h buffer.h
  11. files.o : defs.h buffer.h command.h
  12. utils.o : defs.h
  13. .PHONY : clean
  14. clean :
  15. rm edit $(objects)

        这种方法,也就是 make 的“隐晦规则”。上面文件内容中,.PHONY 表示 clean 是个伪目标文件。关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。

1.5  清空目标文件的规则

        每个 Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。比较稳健的做法是:

  1. .PHONY : clean
  2. clean :
  3. -rm edit $(objects)

        在 rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean 的规则不要放在文件的开头,不然,这就会变成 make 的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean 从来都是放在文件的最后”。

1.6  引用其他的makefile

        在 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 ,那么,下面的语句:

  1. include foo.make *.mk $(bar)
  2. 等价于:
  3. include foo.make a.mk b.mk c.mk e.mk f.mk

        make 命令开始时,会找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。就好像 C/C++ 的 #include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目 录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:

  1. 1、如果 make 执行时,有 -I 或 --include-dir 参数,那么 make 就会在这个参数所指定的目录下去寻找。

  2. 2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也 会去找。

        如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”

二、书写规则

        在 Makefile 中,规则的顺序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的目标都 是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有 很多个,那么,第一个目标会成为最终的目标。make 所完成的也就是这个目标。

2.1  规则的语法

  1. targets : prerequisites ; command
  2. 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 文件

  1. objects := $(patsubst %.c,%.o,$(wildcard *.c))
  2. foo : $(objects)
  3. cc -o foo $(objects)

2.2  文件搜寻

        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> 的文件集的搜索的目录。例如:

  1. vpath %.h ../headers
  2. vpath %.c foo:bar
  3. vpath % blish

2.3  伪目标

        “伪目标”并不是一个文件,只是一个标签,由于“伪目标” 不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

        当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明 一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是“伪目标”。

        伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为 “默认目标”,只要将其放在第一个。如果你的 Makefile 需要一口气生成若干个可执行文 件,但你只想简单地敲一个 make 完事,并且,所有的目标文件都写在一个 Makefile 中,那么你可以使用“伪目标”这个特性:

  1. all : prog1 prog2 prog3
  2. .PHONY : all
  3. prog1 : prog1.o utils.o
  4. cc -o prog1 prog1.o utils.o
  5. prog2 : prog2.o
  6. cc -o prog2 prog2.o
  7. prog3 : prog3.o sort.o utils.o
  8. cc -o prog3 prog3.o sort.o utils.o

        我们知道,Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一 个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到 了我们一口气生成多个目标的目的。.PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显 式“.PHONY : all”不写的话一般情况也可以正确的执行,这样 make 可通过隐式规则推导出,“all”是一个伪目标,执行 make 不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)

2.4  多目标

        Makefile 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不 是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 $@ (关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。

  1. bigoutput littleoutput : text.g
  2. generate text.g -$(subst output,,$@) > $@
  3. 等价于:
  4. bigoutput : text.g
  5. generate text.g -big > bigoutput
  6. littleoutput : text.g
  7. generate text.g -little > littleoutput

        其中,-$(subst output,,$@) 中的 $ 表示执行一个 Makefile 的函数,函数名为 subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思,$@ 表示目标的集合,就像一个 数组,$@ 依次取出目标,并执于命令。

 2.5  静态模式

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>

1、targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
2、target-pattern 是指明了 targets 的模式,也就是的目标集模式。
3、prereq-patterns 是目标的依赖模式,它对 target-pattern 形成的模式再进行一次依赖目标的定义。

看一个例子:

  1. objects = foo.o bar.o
  2. all: $(objects)
  3. $(objects): %.o: %.c
  4. $(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”)。于是,上面的规则展开后等价于下面的规则:

  1. foo.o : foo.c
  2. $(CC) -c $(CFLAGS) foo.c -o foo.o
  3. bar.o : bar.c
  4. $(CC) -c $(CFLAGS) bar.c -o bar.o

2.6  自动生成依赖性(自动找寻头文件)

 gcc -M main.c 的输出是:

  1. main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
  2. /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
  3. /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
  4. /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
  5. /usr/include/bits/sched.h /usr/include/libio.h \
  6. /usr/include/_G_config.h /usr/include/wchar.h \
  7. /usr/include/bits/wchar.h /usr/include/gconv.h \
  8. /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
  9. /usr/include/bits/stdio_lim.h

gcc -MM main.c 的输出则是:

main.o: main.c defs.h

三、书写命令

3.1  显示命令

        通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @ 字符在命令行前,那么,这个命令将不被 make 显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:

@echo 正在编译 XXX 模块......

        当 make 执行时,会输出“正在编译 XXX 模块⋯⋯”字串,但不会输出命令,如果没有“@”,那 么,make 将输出:

echo 正在编译 XXX 模块...... 

正在编译 XXX 模块......

        如果 make 执行时,带入 make 参数 -n 或 --just-print ,那么其只是显示命令,但不会执行令, 这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺 序的。而 make 参数 -s 或 --silent 或 --quiet 则是全面禁止命令的显示。

3.2  命令执行

 示例一:

  1. exec:
  2. cd /home/hchen
  3. pwd

示例二:

  1. exec:
  2. cd /home/hchen;pwd

        当我们执行 make exec 时,第一个例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,而第二个例子中,cd 就起作用了,pwd 会打印出“/home/hchen”。

3.3  嵌套执行make

        在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在 每个目录中都书写一个该目录的 Makefile,这有利于让我们的 Makefile 变得更加地简洁,而不至于把所 有的东西全部写在一个 Makefile 中,这样会很难维护我们的 Makefile,这个技术对于我们模块编译和分 段编译有着非常大的好处。

        例如,我们有一个子目录叫 subdir,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:

  1. subsystem:
  2. cd subdir && $(MAKE)
  3. 其等价于:
  4. subsystem:
  5. $(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 参数 的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

  1. subsystem:
  2. 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 总是失效的。

3.4  定义命令包

        如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以 define 开始,以 endef 结束,如:

  1. define run-yacc
  2. yacc $(firstword $^)
  3. mv y.tab.c $@
  4. endef

        这里,“run-yacc”是这个命令包的名字,其不要和 Makefile 中的变量重名。在 define 和 endef 中 的两行就是命令序列。这个命令包中的第一个命令是运行 Yacc 程序,因为 Yacc 程序总是生成“y.tab.c” 的文件,所以第二行的命令就是把这个文件改改名字。

四、使用变量

        变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的 $ 字符,那么你需要用 $$ 来表示。 变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。

4.1  变量中的变量

        在定义变量的值时,我们可以使用其它变量来构造变量的值,在 Makefile 中有两种方式来在用变量定义变量的值。

        先看第一种方式,也就是简单的使用 = 号,在 = 左侧是变量,右侧是变量的值,右侧变量的值可以 定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:

  1. foo = $(bar)
  2. bar = $(ugh)
  3. ugh = Huh?
  4. all:
  5. echo $(foo)

但这种形式也有不好的地方,那就是递归定义,如:

  1. A = $(B)
  2. B = $(A)

        这会让 make 陷入无限的变量展开过程中去,当然,我们的 make 是有能力检测这样的定义,并会 报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的 make 运行时非常慢,更糟糕的是, 他会使用得两个 make 的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

        为了避免上面的这种方法,我们可以使用 make 中的另一种用变量来定义变量的方法。这种方法使用的是 := 操作符,如:

  1. x := foo
  2. y := $(x) bar
  3. x := later
  4. 其等价于:
  5. y := foo bar
  6. x := later

        值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:

  1. y := $(x) bar
  2. x := foo

那么,y 的值是“bar”,而不是“foo bar”。

        下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:

  1. nullstring :=
  2. 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

4.2 变量的高级用法

        第一种是变量值的替换:我们可以替换变量中的共有的部分,其格式是 $(var:a=b) 或是${var:a=b} ,其意思是,把变量 “var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

  1. foo := a.o b.o c.o
  2. bar := $(foo:.o=.c)

        另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:

  1. foo := a.o b.o c.o
  2. bar := $(foo:%.o=%.c)

        第二种高级用法是——“把变量的值再当成变量”。先看一个例子:在这个例子中,$(x) 的值是“y”,所以 $($(x)) 就是 $(y),于是 $(a) 的值就是“z”。

  1. x=y
  2. y=z
  3. a := $($(x))
  在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:
  1. first_second = Hello
  2. a = first
  3. b = second
  4. all = $($a_$b)

当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:

  1. dir = foo
  2. $(dir)_sources := $(wildcard $(dir)/*.c)
  3. define $(dir)_print
  4. lpr $($(dir)_sources)
  5. endef

4.3 追加变量值

我们可以使用 += 操作符给变量追加值,如:

  1. objects = main.o foo.o bar.o utils.o
  2. objects += another.o
  3. 等价于:
  4. objects = main.o foo.o bar.o utils.o
  5. objects := $(objects) another.o

4.4  多行变量

        还有一种设置变量值的方法是使用 define 关键字。使用 define 关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。

        define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以 [Tab] 键开头,所以如果你用 define 定义的命令变量中没有以 Tab 键开头,那么 make 就不会把其认为是命令。

4.5 目标变量

        我们可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。如:

  1. prog : CFLAGS = -g
  2. prog : prog.o foo.o bar.o
  3. $(CC) $(CFLAGS) prog.o foo.o bar.o
  4. prog.o : prog.c
  5. $(CC) $(CFLAGS) prog.c
  6. foo.o : foo.c
  7. $(CC) $(CFLAGS) foo.c
  8. bar.o : bar.c
  9. $(CC) $(CFLAGS) bar.c

        在这个示例中,不管全局的 $(CFLAGS) 的值是什么,在 prog 目标,以及其所引发的所有规则中 (prog.o foo.o bar.o 的规则),$(CFLAGS) 的值都是 -g。

4.6 模式变量

        我们可以给定一种“模式”,可以把变量定义 在符合这种模式的所有目标上。所以,我们可以以如下方式给所有以 .o 结 尾的目标定义目标变量:

%.o : CFLAGS = -O

五、使用函数

5.1 函数的调用语法

        函数调用,很像变量的使用,也是以 $ 来标识的,其语法如下:

$(<function> <arguments>)

或:${<function> <arguments>}

这里,<function> 就是函数名,make 支持的函数不多。<arguments> 为函数的参数,参数间以逗号 , 分隔,而函数名和参数之间以“空格”分隔。

如:

  1. comma:= ,
  2. empty:=
  3. space:= $(empty) $(empty)
  4. foo:= a b c
  5. bar:= $(subst $(space),$(comma),$(foo))

        在这个示例中,$(comma) 的值是一个逗号。$(space) 使用了 $(empty) 定义了一个空格,$(foo) 的值是 a b c ,$(bar) 的定义用,调用了函数 subst ,这是一个替换函数,这个函数有三个参数,第 一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo) 中的空格替换成逗号,所以 $(bar) 的值是 a,b,c 。​​​​​​​​​​​​​​

5.2 字符串处理函数

5.2.1  subst

$(subst <from>,<to>,<text>)

名称:字符串替换函数
功能:把字串<text>中的<from>字符串替换成<to>。 

返回:函数返回被替换过后的字符串。

5.2.2 patsubst

$(patsubst <pattern>,<replacement>,<text>)

名称:模式字符串替换函数。

功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern> ,如果匹配的话,则以 <replacement> 替换。这里,<pattern> 可以包括通配符 % , 表示任意长度的字串。如果 <replacement> 中也包含 % ,那么,<replacement> 中的这个 % 将是<pattern> 中的那个 % 所代表的字串。(可以用 \ 来转义,以 \% 来表示真实含义的 % 字符)

返回:函数返回被替换过后的字符串。

5.2.3  strip

$(strip <string>)

名称:去空格函数。

功能:去掉<string>字串中开头和结尾的空字符。

返回:返回被去掉空格的字符串值。

5.2.4  findstring

$(findstring <find>,<in>)

名称:查找字符串函数
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。

5.2.5 filter

$(filter <pattern...>,<text>)

名称:过滤函数

功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以 有多个模式。

返回:返回符合模式<pattern>的字串。

5.2.6 filter-out

$(filter-out <pattern...>,<text>)

返回:返回不符合模式<pattern>的字串。

5.2.7 sort

$(sort <list>)

名称:排序函数
功能:给字符串<list>中的单词排序(升序)。

返回:返回排序后的字符串。

备注:sort函数会去掉<list>中相同的单词。

5.2.8 word

$(word <n>,<text>)

名称:取单词函数

功能:取字符串<text>中第<n>个单词。(从一开始)

返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字 符串。

5.2.9 wordlist

$(wordlist <ss>,<e>,<text>)

名称:取单词串函数

功能:从字符串<text>中取从<ss>开始到<e>的单词串。<ss>和<e>是一个数字。

返回:返回字符串<text>中从<ss>到<e>的单词字串。如果<ss>比<text>中的单词数要大, 那么返回空字符串。如果 <e> 大于 <text> 的单词数,那么返回从 <ss> 开始,到 <text> 结束的单词串。

5.2.10 words

$(words <text>)

名称:单词个数统计函数

功能:统计<text>中字符串中的单词个数。

返回:返回<text>中的单词数。

示例:$(words,foo bar baz)返回值是3。

备注:如果我们要取 <text> 中最后的一个单词,我们可以这样:$(word $(words <text>), <text>) 。

5.2.11 firstword

$(firstword <text>)

名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstwordfoobar)返回值是foo。
备注:这个函数可以用word函数来实现:$(word1,<text>)。

5.3 文件名操作函数

$(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。

5.4 foreach 函数和if 函数

$(foreach <var>,<list>,<text>)

把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式。每一次 <text> 会返回一个字符串,循环过程中,<text> 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。

$(if <condition>,<then-part>)

或$(if <condition>,<then-part>,<else-part>)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/641022
推荐阅读
相关标签
  

闽ICP备14008679号