一、Makefile 基础
1.1 make 与 Makefile
在学习 Linux 下的 C/C++ 编程时,一般都是直接通过 gcc 对源文件进行编译的,通过指定 gcc 的参数来指定生成什么样的文件、使用哪个库、在哪个路径搜索等等。但是,在实际的项目工程中包含数百、上千个(甚至更多)源文件,并且不同的源文件包含了不同的库(动态库、静态库、标准库等),又甚至不同的非标准库存放在不同的目录,或者有的源文件要用到多线程。这样的话,如果像初学时一样,在 shell 下使用 gcc 命令编译异的话,要使用无数的参数去指定不同路径,不同的链接库等等。这么繁琐复杂的命令显然是不太合理的,并且每次编译都要来这么一遍大大消耗了时间成本。make命令 和 Makefile 就是用来解决型项目开发过程中的编译问题的。
make 工具可以认为是一个智能批处理工具,make 工具本身并么有编译和链接的功能,而是用类似于批处理的方式通过调用 Makefile 文件中指定的命令来进行编译和链接的;它解释 Makefile 中的规则指令,Makefile 文件负责向 make 提供如何去执行的规则。在 Makefile 文件中描述了整个工程所有文件的编译顺序、编译规则等。
Makefile 有自己的书写格式、关键字、函数,就像任何一门编程语言有自己的语法一样。而且在 Makefile 中可以使用 shell 的命令来完成某些工作,也就是说 Makefile 中可以使用 shell 命令,比如说,编译完成后删除所有的中间文件,可以使用 rm -f *.o
这样的 shell 命令。
在一个工程中,源文件很多,按类型、功能、模块分别被存放在若干目录中,需要按一定顺序、规则进行编译,这时就需要使用到 Makefile。
Makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要重新编译,如何进行链接等操作。
Makefile 就是“自动化编译”,告诉 make 命令如何编译和链接。
Makefile 是 make 工具的配置脚本,默认情况下,make 命令会在当前目录下寻找该文件(按顺序找寻文件名为 “Makefile”、“makefile”、“GNUmakefile” 的文件)。也可使用 make 的 -f
参数指定 Makefile 的路径,如 make -f /home/user/Makefile
或 make -f make.debug
。
Makefile 在绝大多数的集成开发环境中也都在使用的,只不过我们看不到而已,可以说,Makefile 几乎已经成为一种工程编译的基本方法。
通过 Makefile 可以制定好相应的编译与连接规则,先编译哪个文件后编译哪个文件、哪个需要编译哪个不需要编译、如何链接、如何生成、要生成什么文件等等全部都在 Makefile 文件中提前制定好,在编译的时候只需要使用 make 工具,执行 make 命令就可以了。
另外,在项目开发中难免会对源码进行修修改改,如果每次修改都要重新编译所有的源文件,那么将浪费大量的时间,我们可以在 Makefile 中制定规则,只去编译被修改的源文件,其他文件不需要重新编译,。总之,有了 Makefile 大型项目的编译效率将大大提高。
1.2 Makefile 文件格式
Makefile 文件中记录了工程的构建规则及相关辅助信息,内容包含 显示规则、隐晦规则、变量定义、文件知识、注释 等信息。
Makefile 文件由一系列的规则构成,每条规则的格式如下:
|
|
目标 是必须的,不可省略;前置条件 和 命令 都是可选的,但是两者之中必须至少存在一个。
若 prerequisites 与 commonds 在同一行,需要用 ;
分隔。若 prerequisites 与 commonds 不在同一行,则 commonds 前面需要用 Tab 键开头。
Makefile 中有三要素:
- 目标(指明要干什么,即运行 make 后生成什么)
- 依赖(用什么去生成)
- 命令(如何生成目标)
在 Makefile 中,目标和依赖是通过规则(rule)来表达的。目标(指明要干什么,即运行 make 后生成什么)、依赖(用什么去生成)和 命令(如何生成目标)这三个要素组成一个规则。实际上,三要素中必不可少的是目标 和 命令,依赖可以没有,这一点在后面的实战编写 Makefile 的时候会有体现。
Makefile的一个规则是由目标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。需要指出的是,目标和先决条件之间表达的就是依赖关系(dependency),这种依赖关系指明在构建目标之前,必须保证先决条件先满足(或构建)。而先决条件可以是其它的目标,当先决条件是目标时,其必须先被构建出来。还有就是一个规则中目标可以有多个,当存在多个目标,且这一规则是Makefile 中的第一个规则时,如果运行make 命令不带任何目标,那么规则中的第一个目标将被视为是缺省目标。
对于 Makefile 的命名,可以是 Makefile 也可以是 makefile 或者 GNUmakefile,三种方法都可以,但也只能是这三种。这样在 shell 中执行 make 命令就会直接使用这个 makefile 文件。当然,如果你取了其它名字也是可以的,不过要在 make 命令的时候要显示指出文件,如下所示:
|
|
Mikefile 示例1:
|
|
需要注意的是echo 前面必须只有 Tab(即键盘TAB键),且至少有一个 Tab,而不能用空格代替。
Tips: makefile 中所有以 Tab 开始的行,make 都会交给 shell 去处理,所以在命令的前面一定要以 Tab 键开头。
执行结果:
|
|
Makefile 中的 all
就是目标,目标放在 :
的前面,其名字可以是由字母和下划线_
组成 。echo "hello all"
就是生成目标的命令,这些命令可以是任何可以在你的环境中运行的命令以及 make 所定义的函数等等。
all
目标在这里就是代表希望在终端上打印出“hello all”,有时目标会是一个比较抽象的概念。all 目标的定义,其实是定义了如何生成 all 目标,这也称之为规则(rule)。
Mikefile 示例2,在前面示例的基础上调换目标位置:
|
|
执行结果:
|
|
由以上示例可知,一个 Makefile 中可以定义多个目标,上述示例中对于了 all 、test 连个目标。调用 make 命令时,得告诉它我们的目标是什么,即要它干什么。当没有指明具体的目标是什么时,那么 make 以 Makefile 文件中定义的第一个目标作为这次运行的目标,这“第一个”目标也称之为默认目标(和是不是all没有关系)。
当 make 确定目标后,从 Makefile 中先找到定义目标的规则,然后运行规则中的命令来达到构建目标的目的。上面所示例的 Makefile 中,每一个规则中都只有一条命令,而实际的 Makefile,每一个规则可以包含很多条命令。
Tips: 命令前加了一个
@
,这一符号告诉 make,在运行时不要将这一行命令显示出来。
Mikefile 示例3:
|
|
执行结果:
|
|
当运行 make 时,test 目标也被构建了。这里要引入 Makefile 中依赖关系的概念,all 目标后面的 test 是告诉 make,all 目标依赖 test 目标,这一依赖目标在 Makefile 中又被称之为先决条件。
出现这种目标依赖关系时,make工具会按从左到右的先后顺序先构建规则中所依赖的每一个目标。上面示例3中表名,如果希望构建 all 目标,那么 make 会在构建它之前先构建 test 目标(因为test 是all 的依赖条件),这就是为什么称之为先决条件的原因。
1.3 Makefile 的工作原理
在执行 make 命令时,首先,make 会先去比较目标文件和依赖文件的修改日期,如果依赖文件的日期要比目标文件的日期新,或者目标文件不存在,那么 make 就会执行后面的命令。假如说在目标的后面没有依赖,比如我们经常用伪目标 clean 去清除中间文件,当 make 发现先冒号后面没有依赖的时候,它默认是不会执行后面的命令的,除非在 make 后面显示的指出这个目标的名字,这也就是我们经常使用的 make clean 命令。
总结来说,Makefile 的工作原理可以理解为它是根据依赖去递推的。
make 命令执行过程(不指定目标):
- 首先 make 工具会在当前目录查找名为 makefile 或 Makefile 的文件,如果我们在 make 命令后面使用
-f
参数指定了文件名,make 就在当前目录查找指定的文件名。 - 如果找到了 makefile 文件,那么会先查找文件中的第一个目标进行如下处理:
- 如果目标不存在,检查目标是否有依赖:
- 如果目标没有依赖,直接执行生成目标的命令
- 如果目标存在依赖,则根据依赖的顺序,递推处理依赖,处理完依赖后执行生成目标的命令
- 如果目标已经存在,则检查目标是否有依赖:
- 如果目标没有依赖,提示
make: “目标”是最新的。
后退出 - 如果目标存在依赖,则根据依赖的顺序,递推处理依赖,处理完依赖后,判断依赖文件的更新时间比目标文件的更新时间新,那么就执行后面的命令重新生成目标文件
- 如果目标没有依赖,提示
- 如果目标不存在,检查目标是否有依赖:
- 如果上一个目标文件的依赖存在,那么 make 会递推查找依赖文件的依赖,然后重复上面的操作。
所以说,makefile 是根据依赖一层一层递推的,不停的去递推寻找依赖。make 只负责在 makefile 中递推寻找依赖,并根据依赖执行命令,而不关心编译是否成功,只要最终的依赖可以找到,就能执行成功,如果最终的依赖没找到,那么 make 就会直接退出。
而对于伪目标的执行,可以直接在 make 后面指定目标,这样即使目标后面没有依赖,也会执行命令。
正是 make 的这种依赖递推查找特性,以及根据更新时间决定是否生成的特性,我们可以把依赖分解,这样就能做到某单个源文件修改可以只编译这一个源文件,而不必所有源文件都重新编译。
1.4 多文件编译
目前有两个源文件,需要编译成一个应该程序:
|
|
|
|
编译时它们的依赖关系可总结为如下图所示: 根据依赖关系编写 Makefile :
|
|
我们增加了一个 clean 目标用于删除所生成的文件,包括目标文件和 simple 可执行程序,这在现实的项目中很是常见。
值得注意的是,如果执行两次make会怎么样?执行效果如下:
|
|
第二次编译并没有构建目标文件的动作,但有构建simple可执行程序的动作,我们需要了解 make 是如何决定哪些目标(这里是文件)是需要重新编译的。
为什么 make 会知道我们并没有改变 main.c 和 foo.c 呢?
答案是通过文件的时间戳,当 make 在运行时,make 处理一个规则采用的方法是:判断先决条件和目标的时间,如果先决条件中相关的文件的时间戳大于目标的时间戳,即先决条件中的文件比目标更新,则说明先决条件有变化,那么需要运行该规则当中的命令重新构建目标。
这一规则会运用到所有在 make 时指定的目标及依赖树中的每一个规则。比如,对于 simple 项目,其依赖树中包括三个规则,make 会检查所有三个规则当中的目标(文件)与先决条件(文件)之间的时间先后关系,从而来决定是否要重新创建规则中的目标。
为什么会第二次make时会执行 gcc -o simple main.o foo.o
呢?
因为 all文件 在我们的编译过程中并不生成,即 make 在第二次编译时找不到 all 文件,所以又重新编译了一遍。如果把 all改为 simple,那就是我们所期望的结果:
|
|
执行结果:
|
|
另外,对于 make 工具,一个文件是否改动不是看文件大小,而是其时间戳。比如用 touch
命令来改变文件的时间戳就行了,这相当于模拟了对文件进行了一次编辑,而不需真正对其进行编辑。make 发现了 foo.c 需要重新被编译,而这,最终也导致了 foo.o 和 simple 需要重新被编译。
|
|
需要特别指出的是,在规则中有些依赖虽然可以省略不写,但是这将导致依赖的更新不会被目标所识别,如下:
|
|
在上面的 make 规则中, simple 目标依赖 main.o 和 foo.o 如果被省略,make 将不会推导出该 simple目标 的先决条件 main.o 和 foo.o 两个子目标 并 先生成 main.o 和 foo.o 目标, 而直接执行 gcc -o simple main.o foo.o
,此时并不存在 main.o 和 foo.o 两个文件,这将导致 simple 目标命令执行以失败退出。
main.o 和 foo.o 两个目标的依赖可以省略,但是当省略依赖后,对 main.c 或 foo.c 的修改,将不会 自动更新 main.o 或 foo.o,如下:
|
|
虽然 touch foo.c
更新了 foo.c,但是由于 foo.o 目标的依赖被省略,使得 make 不知道 foo.o 的依赖已经更新,而不会重新执行命令生成更新后的目标。
1.5 伪目标 .PHONY
在前面的示例项目中,假设在程序所在的目录下面有一个 clean 文件,这个文件也可以通过 touch 命令来创建。创建以后,运行 make clean
命令:
|
|
会发现 make 总是提示 clean 文件是最新的,而不是按我们所期望的那样进行文件删除操作。这是因为 make 将 clean 当作文件,且在当前目录找到了这个文件,加上 clean 目标没有任何先决条件,所以,当我们要求 make 为我们构建 clean 目标时,它就会认为 clean 是最新的。
对于这种情况,在现实中也难免存在所定义的目标与所存在的文件是同名的,采用 Makefile 如何处理这种情况呢?
Makefile 中的伪目标(phony target)可以解决这个问题。伪目标可以采用 .PHONY
关键字来定义,需要注意的是其必须是大写字母。伪目标 用法如下:
|
|
将 clean 变为假目标后的 Makefile,更改后运用 make clean 命令的结果:
|
|
采用 .PHONY
关键字声明一个目标后,make 并不会将其当作一个文件来处理,而只是当作一个概念上的目标。对于假目标,我们可以想像的是由于并不与文件关联,所以每一次 make 这个假目标时,其所在的规则中的命令都会被执行。
二、Makefile 中的变量
2.1 Makefile 中的变量简介
在 Makefile 中通过使用变量来使得它更简洁、更具可维护性。
|
|
在 Makefile 中,一个变量的定义很简单,就是一个名字(变量名)后面跟上一个等号,然后在等号的后面放这个变量所期望的值。对于变量的引用,则需要采用 $(变量名)
或者 ${变量名}
这种模式。
如上所示,采用变量的话,当需要更改编译器时,只需更改变量赋值的地方,非常方便,如果不采用变量,那得更改每一个使用编译器的地方,很是麻烦。显然,变量的引入增加了 Makefile 的可维护性。既然定义了一个 CC 变量,当然也可以将 -o
或是 -c
命令参数也定义成为一个变量,因为如果更改了一个编译器,那么很有可能其使用参数也得跟着改变。
变量的赋值就是在变量后面写上值文本字符串,在使用时直接用后面的文本字符串去替换变量本身。Makefile中的变量的赋值方式有四种:
=
只用一个=
符号定义的变量,称之为递归扩展变量(recursively expanded variable), 也称为递归赋值,定义时并不真正赋值,在实际使用时才会进行展开
|
|
|
|
Tips: 递归扩展变量的引用是递归的。这种递归性有利也有弊。利的方面是最后foo将会被展开。但也存在弊,那就是我们不能对foo变量再采用赋值操作。如下的方式会出现一个死循环:
|
|
:=
简单赋值,是一种最普通的赋值,立即替换,也就是说在变量赋值的时候,立即把变量展开为后面的值,或者说当前的赋值只对当前的语句有效,和后面对该变量的赋值无影响
|
|
|
|
?=
条件赋值,如果变量是第一次赋值,则赋值生效,否则赋值无效。
|
|
|
|
+=
追加赋值,在变量后面追加一个值,用空格与前面的值分隔开
|
|
|
|
2.2 Makefile 自动变量
自动变量 是指 makefile 根据模式规则自动推导的变量,这类变量只能在命令中使用。实际上,自动化变量属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。下面是常用的自动化变量列表:
$@
用于表示当前目标,当一个规则中有多个目标时,$@
所指的是其中任何造成命令被运行的目标。$^
则表示的是规则中的所有先决条件。$<
表示的是规则中的第一个先决条件。$?
指代比目标更新的所有前置条件,之间用空格分隔。比如,规则为t:p1 p2
,其中 p2 的时间戳比 t 新,那么$?
就指代 p2。
其它自动化变量列表:
自动化变量 | 说明 |
---|---|
$% |
当目标文件是一个静态库文件时起作用,代表静态库的一个成员名,比如目标是 1.a 那么 $% 表示 1.o, $@ 表示 1.a |
$+ |
类似“$^”,但是它保留了依赖文件中重复出现的文件(主要用在程序链接时库的交叉引用场合),也就是说他也代表所有依赖文件,但是不会去除重复文件 |
$* |
在模式规则和静态模式规则中,代表茎,茎是目标模式中 % 所代表的部分 |
$(@D) |
表示文件的目录部分(不以斜杠结尾),如果 $@ 表示的是 dir/1.c 那么 $(@D) 表示的值就是目录 dir |
$(@F) |
表示的是文件除目录外的部分即文件名,如果 $@ 表示的是 dir/1.c,那么 $@F 表示的是 1.c |
$(*D) $(*F) |
分别代表茎中的目录部分和文件名部分 |
$(%D) $(%F) |
当目标是静态库文件时,分别表示库文件成员中的目录部分和文件名部分 |
$(<D) $(<F) |
分别表示第一个依赖文件的目录部分和文件名部分 |
$(^D) $(^F) |
分别表示所有依赖文件的目录部分和文件部分(无重复文件) |
$(+D) $(+F) |
分别表示所有的依赖文件的目录部分和文件部分(保留了依赖文件中重复出现的文件) |
$(?D) $(?F) |
分别表示更新的依赖文件的目录部分和文件名部分 |
我们就可以将simple项目的Makefile改为如下:
|
|
自动变量在对它们还不熟悉时,看起来可能有那么一点吃力,但熟悉了你就会觉得其简捷(洁),那时也会觉得它们好用。
2.3 Makefile 特殊变量
在 Makefile 中有几个特殊变量,可能经常需要用到。
- 第一个就是
MAKE
变量,它表示的是 make 命令名是什么。当我们需要在 Makefile 中调用另一个 Makefile 时需要用到这个变量,采用这种方式,有利于写一个容易移植的 Makefile。
|
|
- 第二个特殊变量则是
MAKECMDGOALS
,它表示的是当前用户所输入的 make 目标是什么(从命令行输入的目标,由参数传递给 make 命令的目标)。
|
|
执行
|
|
Tips:
MAKECMDGOALS
指的是用户输入的目标,当只运行 make 命令时,虽然根据 Makefile 的语法,第一个目标将成为缺省目标,即 all 目标,但MAKECMDGOALS
仍然是空,而不是 all,这一点需要注意。
2.4 变量及其值的来源
在 Makefile 中我们可以对变量进行定义并赋值。此外,还有其它的地方让 Makefile 获得变量及其值。比如:
- 对于前面所说到的自动变量,其值是在每一个规则中根据规则的上下文自动获得变量值的。
- 可以在运行 make 时,在 make 命令行上定义一个或多个变量。在 make 命令行中定义的变量及其值同样在 Makefile 中是可见的。在 make 命令行中定义的变量将覆盖 在Makefile 中所定义的同名变量的值。
|
|
- 变量还可以来自于 Shell 环境,例如采用 Shell 中的
export
命令定义了一个变量后再执行Makefile。
|
|
2.5 变量引用的高级功能
变量可以在赋值的同时完成后缀替换操作:
|
|
执行结果:
|
|
bar 变量中的文件名从 .o
后缀都变成了 .c
。这种功能也可以采用 patsubst
函数来实现,与函数相比,这种功能更加的简洁。当然,patsubst
功能更强,而不只是用于替换后缀。
2.6 override 指令
前面了解到,可以采用在 make 命令行上定义变量的方式,使得 Makefile 中定义的变量覆盖掉,从而不起作用。可能,在设计 Makefile 时,并不希望用户将我们在 Makefile 中定义的某个变量覆盖掉,那就得用 override 指令了。
|
|
执行:
|
|
三、makefile 的字符匹配和文件搜索
3.1 字符匹配
字符匹配首先想到的就是通配符,因为 Makefile 中使用的是 shell 中的命令,所以 shell 中的通配符在 Makefile 中也适用。我们在Makefile中使用的通配符主要有两个:
*
:匹配任意个字符?
:匹配一个字符
比如说,依赖是所有的 .c
文件,就可以用通配符来表示 *.c
,但是如果我们在定义变量的时候要使用通配符的话,要注意一点,如果我们直接把 *.c
直接使用等号赋值给变量的话,这个变量会默认去匹配文件名为 *.c
的文件,例如:
|
|
Src
变量表示 *.c
文件
要想使变量 Src
表示所有源文件,也就是让 *
作为通配符而不是文件名,需要借助一个函数 wildcard
,该函数就是表示通配符的意思,具体使用将在后面的函数章节介绍
|
|
Src变量表示所有后缀为 .c
的文件。
还有一个通配符 [ ]
并不常用,在中括号中可以指定匹配的字符。比如说,[a-z]
表示匹配 a
到 z
中任何一个字符。
第二种用于字符匹配的是 %
,%
字符作用类似于通配符 *
,它和 *
的区别是,模式匹配字符可以对目标文件与依赖文件进行匹配。比如说我们在写 makefile 的时候,经常会写这样的一条规则
|
|
这里的 %
代表的是一个文件名,也就是一个字符串。首先,所有的 .o
文件会组成一个列表,然后挨个被拿出来,%
表示当前拿出来的 %.o
文件的文件名,然后根据文件名 %
来寻找和 .o
文件同名的 %.c
文件,并把取出的 %.o
文件和寻找到的 %.c
文件用于执行后面的命令。这是 makefile 中自动匹配的一种规则。
对于前面的 Makefile,其中存在多个规则用于构建目标文件。比如,main.o 和 foo.o 都是采用不同的规则进行描述的。如果对于每一个目标文件都得写一个不同的规则来描述,太繁了!对于一个大型项目,就更不用说了。Makefile 中的模式就是用来解决这种烦恼的。我们可以把之前的simple项目的Makefile改成这样:
|
|
与 前一版本的 Makefile 相比,最为直观的改变就是从二条构建目标文件的规则变成了一条。。采用了模式以后,不论有多少个源文件要编译,我们都是应用同一个模式规则的,很显然,这大大的简化了我们编写Makefile的工作。使用了模式规则以后,同样可以用这个 Makefile 来编译或是清除 simple 项目,这与前一版本在功能上是完全一样的。
3.2 文件搜索
默认情况下,make 会在 makefile 文件所在目录进行搜索规则中所用到的文件,如果我们把所有的文件都和 makefile 文件放在同一个目录下,那肯定是没有问题的,但是实际开发中,我们用到的源文件、头文件、库文件可能会根据用途和种类分别位于不同的目录下,所以这就需要有文件搜索的功能。makefile 中文件搜搜主要有两种方法,一个是环境变量 VPATH
一个是关键字 vpath
:
VPATH
环境变量的用法如下
|
|
Tips: 当使用环境变量指定上面的路径后,make 会现在当前目录搜索,然后去目录
/mkdir1/
搜索,然后再去/mkdir2/
搜索,搜索的顺序是先当前目录,然后按照变量赋值中的顺序去搜索。这里的:=
是变量赋值的一种方式,表示在定义时立即展开应用的变量。另外,不同的目录之间要用:
或者空格隔开。 附:变量赋值的几种方式(后面详细介绍)
vpath
关键字
在上面的环境变量中,VPATH
是搜索指定路径的所有文件, vpath
关键字的搜索方式是选择性搜索,使用方法如下:
|
|
四、Makefile中的函数
4.1 Makefile中的函数简介
Makefile 也支持函数,可以通过函数来控制变量,函数的使用和变量类似,需要 $()
或 ${}
来标识,如果函数有参数的话直接在函数后面列出参数,参数之间用 ,
隔开,比如 $(func arg1, arg2)
。
函数是 Makefile 中的另一个利器,现在看一看采用函数如何来简化 simple 项目的 Makefile。对于 simple 项目的 Makefile,尽管使用了模式规则,但还有一件比较恼人的事,得在这个Makefile 中指明每一个需要被编译的源程序。对于一个源程序文件比较多的项目,如果每增加或是删除一个文件都得更新 Makefile,其工作量也不可小视!
下面是采用了 wildcard
和 patsubst
两个函数后 simple 项目的 Makefile。需要注意的是函数的语法形式很特别,不过只要记住其形式就行了。
|
|
下面根据用途分类来介绍 makefile 中的函数。
4.2 字符串处理函数
1、模式字符替换函数 patsubst
- 函数原型:
|
|
- 函数功能:查找 text 中的单词(单词以空格、Tab 或回车换行分隔)是否符合模式 pattern,如果匹配的话,则用 replacement 替换。pattern 可以包括通配符
%
,表示任意长度的字符串。如果 replacement 中也包含%
,那么,replacement 中的这个%
将是 pattern 中的那个%
所代表的字符串。 - 函数返回:返回值为替换后的新字符串。
- 用法示例:
|
|
执行:
|
|
可以看出采用 patsubst 函数进行字符串替换时,我们希望将所有的 .c
文件都替换成 .o
文件。当然,由于 patsubst 函数可以使用模式,所以其也可以用于替换前缀等等,功能更加的强。
2、
addprefix 函数 addprefix 函数是用来在给字符串中的每个子串前加上一个前缀,其形式是:
|
|
示例:
|
|
执行:
|
|
4.3 filter 和 filter-out 函数
filter 函数 用于从一个字符串中,根据模式得到满足模式的字符串,其形式是:
|
|
示例:
|
|
执行:
|
|
从结果来看,经过 filter 函数的调用以后,source变量中只存在 .c
文件和 .s
文件了,而 .h
文件则被过滤掉了。
filter-out 函数 用于从一个字符串中根据模式滤除一部分字符串,其形式是:
|
|
示例:
|
|
执行:
|
|
从结果来看,filter-out 函数将 main1.c 和 main2.c从 src变量中给滤除了。filter 与 filter-out 是互补的。
4.4 patsubst 函数
patsubst 函数是用来进行字符串替换的,其形式是:
|
|
示例:
4.5 strip函数
strip 函数用于去除变量中的多余的空格,其形式是:
|
|
示例:
|
|
执行:
|
|
从结果来看,strip 函数将 foo.c 和 bar.c 之间的多余的空格给去除了。
4.6 wildcard 函数
wildcard 是通配符函数,通过它可以得到我们所需的文件,这个函数如果我们在 Windows 或是Linux 命令行中的 *
。其形式是:
|
|
示例:
|
|
执行:
|
|
从当前 Makefile 所在的目录下通过 wildcard 函数得到所有的 C 程序源文件。
五、参考博文
- 深度刨析makefile: https://mp.weixin.qq.com/s/8qT6xd163yQOrwUISFt16w
- Makefile 介绍: https://blog.csdn.net/Yang_Mao_Shan/article/details/131696714