一、Makefile文件是什么?
Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目。一旦编写好 Makefile 文件,只需要一个make
命令,整个工程就开始自动编译,不再需要手动执行 GCC 命令。
一个中大型 C/C++ 工程的源文件有成百上千个,它们按照功能、模块、类型分别放在不同的目录中,Makefile 文件定义了一系列规则,指明了源文件的编译顺序、依赖关系、是否需要重新编译等。
二、Makefile基本规则
Makefile基本规则由两个部分组成,分别是依赖的关系和执行的命令,其结构如下所示:
targets : prerequisites
command
- targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
- prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
- command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
注:命令的开始一定要使用
Tab
键。
三、Makefile工作流程
当在 shell 提示符下输入make
命令以后, make 读取当前目录下的 Makefile 文件,并将其中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。
Makefile 的具体工作流程可以通过例子来看一下:
main:main.o test1.o test2.o
gcc main.o test1.o test2.o -o main
main.o:main.c test.h
gcc -c main.c -o main.o
test1.o:test1.c test.h
gcc -c test1.c -o test1.o
test2.o:test2.c test.h
gcc -c test2.c -o test2.o
make 在执行这个规则所定义的命令之前,首先处理目标的所有的依赖文件,有下列三种情况:
- 目标 “.o” 文件不存在,使用其描述规则创建它;
- 目标 “.o” 文件存在,目标 “.o” 文件所依赖的 “.c” “.h” 源文件中的任何一个比目标 “.o” 文件更新(即在上一次 make 之后被修改),则根据规则重新编译生成它,否则什么也不做;
我们在使用的时候会产生中间文件会让整个文件看起来很乱,所以在编写 Makefile 文件的时候会在末尾加上这样的规则语句:
.PHONY:clean #表示clean是一个伪目标
clean:
rm -rf *.o test
其中 “*.o” 是执行过程中产生的中间文件,“test” 是最终生成的执行文件。
在shell 中执行 “make clean” 命令,编译时的中间文件和生成的最终目标文件都会被清除,方便我们下次的使用。
四、Makefile变量的定义和使用
1. 变量的定义
Makefile中的变量实际上就是一个字符串。变量的名称可以由大小写字母、阿拉伯数字和下划线构成。至于值列表,既可以是零项,也可以是一项或者是多项。如:
VALUE_LIST = one two three
调用变量的时候可以用 “$(VALUE_LIST)” 或者是 “${VALUE_LIST}” 来引用。
2. 变量的基本赋值
Makefile 的变量有四种基本赋值方式:
- 简单赋值 ( := ) :编程语言中常规理解的赋值方式,只对当前语句的变量有效。
- 递归赋值 ( = ) :赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
- 条件赋值 ( ?= ) :如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
- 追加赋值 ( += ) :原变量用空格隔开的方式追加一个新值。
不同的赋值方式会产生不同的结果,我们使用的时候应该根据具体的情况选择相应的赋值规则。
3. 变量的替换引用
foo:=a.c b.c d.c
obj:=$(foo:%.c=%.o)
All:
@echo $(obj)
这段代码实现的功能是字符串的后缀名的替换,把变量 foo 中所有的以 .c 结尾的字符串全部替换成 .o 结尾的字符串。
注意:括号中的变量使用的是变量名而不是变量名的引用,变量名的后面要使用冒号和参数选项分开,表达式中间不能使用空格。
五、Makefile自动化变量
自动化变量可以理解为由 Makefile 自动产生的变量。自动化变量的取值根据执行的规则来决定,取决于执行规则的目标文件和依赖文件。
下面是对所有的自动化变量进行的说明:
自动化变量 | 说明 |
---|---|
$@ | 表示规则的目标文件名。 |
$% | 仅当目标是函数库文件中,表示规则中的目标成员名。 |
$< | 规则的第一个依赖的文件名。 |
$? | 所有比目标文件更新的依赖文件列表,空格分隔。 |
$^ | 代表的是所有依赖文件列表,使用空格分隔。 |
$+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。 |
$* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分 |
六、Makefile目标文件搜索
常见的搜索的方法的主要有两种:一般搜索VPATH
和选择搜索vpath
。
VPATH 是变量,更具体的说是环境变量,Makefile 中的一种特殊变量,使用时需要指定文件的路径。例如:
VPATH := src #所有文件都会进入src目录进行搜索
vpath 是关键字,按照模式搜索,也可以说成是选择搜索。搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件。例如:
vpath %.c src #只有.c文件才会进入src目录进行搜索
七、Makefile条件判断
条件语句可以根据一个变量的值来控制 make 执行或者时忽略 Makefile 的特定部分,条件语句可以是两个不同的变量或者是常量和变量之间的比较。
下面是条件判断中使用到的一些关键字:
关键字 | 功能 |
---|---|
ifeq | 判断参数是否不相等,相等为 true,不相等为 false。 |
ifneq | 判断参数是否不相等,不相等为 true,相等为 false。 |
ifdef | 判断是否有值,有值为 true,没有值为 false。 |
ifndef | 判断是否有值,没有值为 true,有值为 false。 |
实例:
foo:$(objects)
ifeq($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(noemal_libs)
八、Makefile常用字符串处理函数
1. 模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)
函数说明:查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。返回值为替换后的新字符串。
2. 字符串替换函数
$(subst <from>,<to>,<text>)
函数说明:把字符串中的 form 替换成 to,返回值为替换后的新字符串。
3. 去空格函数
$(strip <string>)
函数说明:去掉字符串中多余的空格,将其中的多个连续的空格合并成为一个空格。返回值为去掉空格后的字符串。
4. 查找字符串函数
$(findstring <find>,<in>)
函数说明:查找 in 中的 find,如果查找的目标字符串存在,返回值为目标字符串,如果不存在就返回空。
5. 过滤函数
$(filter <pattern>,<text>)
函数说明:过滤出 text 中符合模式 pattern 的字符串,可以有多个 pattern 。返回值为过滤后的字符串。
6. 反过滤函数
$(filter-out <pattern>,<text>)
函数说明:去除符合模式 pattern 的字符串,保留不符合的字符串。返回值是保留的字符串。
7. 排序函数
$(sort <list>)
函数说明:函数的功能是将 list 中的单词排序(升序)。返回值为排列后的字符串。
注意:sort会去除重复的字符串。
8. 取单词函数
$(word <n>,<text>)
函数说明:函数的功能是取出函数 text 中的第n个单词。返回值为取出的第 n 个单词。
九、Makefile常用文件名操作函数
1. 取目录函数
$(dir <names>)
函数说明:从文件名序列 names 中取出目录部分。返回值为文件目录部分,指的是最后一个反斜杠之前的部分。
2. 取文件函数
$(notdir <names>)
函数说明:从文件名序列 names 中取出非目录的部分。返回值为文件非目录的部分,指的是最后一个反斜杠之后的部分。
3. 取后缀函数
$(suffix <names>)
函数说明:从文件名序列中 names 中取出各个文件的后缀名。返回值为 names 中的后缀序列,如果文件没有后缀名,则返回空字符串。
4. 取前缀函数
$(basename <names>)
函数说明:从文件名序列中 names 中取出各个文件的前缀名。返回值为 names 中的前缀序列,如果文件没有前缀名,则返回空字符串。
5. 添加后缀函数
$(addsuffix <suffix>,<names>)
函数说明:把后缀 suffix 加到 names 中的每个单词后面。返回值为添加上后缀的文件名序列。
6. 添加前缀函数
$(addperfix <prefix>,<names>)
函数说明:把前缀 prefix 加到 names 中的每个单词的前面。返回值为添加上前缀的文件名序列。
7. 链接函数
$(join <list1>,<list2>)
函数说明:把 list2 中的单词对应的拼接到 list1 的后面。返回值为拼接好的字符串。
8. 获取匹配模式文件名函数
$(wildcard PATTERN)
函数说明:列出当前目录下所有符合模式的 PATTERN 格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名。
十、Makefile中的其它常用函数
1. foreach
$(foreach <var>,<list>,<text>)
函数说明:把参数<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。每一次<text>
会返回一个字符串,循环过程中,<text>
的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
2. if
$(if <condition>,<then-part>) 或
$(if <condition>,<then-part>,<else-part>)
函数说明:condition
参数是 if 表达式,如果其返回的是非空的字符串,那么这个表达式就相当于返回真,于是,then-part
就会被计算,否则else-part
会被计算。而if函数的返回值是:如果condition
为真(非空字符串),那么then-part
会是整个函数的返回值,否则为else-part
。
3. origin
$(origin <variable>)
函数说明:origin 函数不像其他的函数,它并不操作变量的值,它只是告诉你这个变量是哪里来的。
注意: variable 是变量的名字,不应该是引用,所以最好不要使用“$”字符。
十一、Makefile include文件包含
当 make 读取到 “include” 关键字的时候,会暂停读取当前的 Makefile,而是去读 “include” 包含的文件,读取结束后再继读取当前的 Makefile 文件。
“include” 使用的具体方式如下:
include <filenames>
filenames 是 shell 支持的文件名(可以使用通配符表示的文件)。使用 “include” 包含进来的 Makefile 文件中,如果存在函数或者是变量的引用,它们会在包含的 Makefile 中展开。
include 通常使用在以下的场合:
- 在一个工程文件中,每一个模块都有一个独立的 Makefile 来描述它的重建规则。它们需要定义一组通用的变量定义或者是模式规则。通用的做法是将这些共同使用的变量或者模式规则定义在一个文件中,需要的时候用 “include” 包含这个文件。
- 当根据源文件自动产生依赖文件时,我们可以将自动产生的依赖关系保存在另一个文件中。然后在 Makefile 中包含这个文件。
执行 make 命令的时候可以加入选项 “-I” 或 “–include-dir” 后面添加上指定的路径
十二、Makefile嵌套执行
在一个大的工程文件中,不同的文件按照功能被划分到不同的模块中,也就说很多的源文件被放置在了不同的目录下。每个模块可能都会有自己的编译顺序和规则,也就是每一个模块都有一个 Makefile 文件。我们只需要在顶层Makefile中控制其他模块中的 Makefile 就可以实现总体的控制,这就是 make 的嵌套执行。
如何来使用呢?举例说明如下:
subsystem:
cd subdir && $(MAKE)
这个例子可以这样来理解,在当前目录下有一个目录文件 subdir 和一个 Makefile 文件,子目录 subdir 文件下还有一个 Makefile 文件,这个文件是用来描述这个子目录文件的编译规则。
使用时只需要在最外层的目录中执行 make 命令,当命令执行到上述的规则时,程序会进入到子目录中执行 make。这就是嵌套执行 make,我们把最外层的 Makefile 称为是总控 Makefile。
上述的规则也可以换成另外一种写法:
subsystem
$(MAKE) -C subdir
使用 make 嵌套执行的时候,可以使用export
来传递变量。
注意:SHELL 和 MAKEFLAGS变量总会传递到下层的 Makefile 中