Makefile 学习
March 2020
Table of Contents
Makefile简介
Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。Makefile在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。
Makefile工作规则
我们编译项目文件的时候,默认情况下,make 执行的是 Makefile 中的第一规则(Makefile 中出现的第一个依赖关系),此规则的第一目标称之为“最终目标”或者是“终极目标”。
在 shell 命令行执行我们的 make 命令。可以得到可执行文件 “main” 和中间文件 “main.o”、”test1.o” 和 “test2.o”,main 就是我们要生成的最终文件。通过 Makefile 我们可以发现,目标 “main” 在 Makefile 中是第一个目标,因此它就是 make 的终极目标,当修改过任何 C 文件后,执行 make 将会重建终极目标 “main”。
它的具体工作顺序是:当在 shell 提示符下输入 make 命令以后。 make 读取当前目录下的 Makefile 文件,并将 Makefile 文件中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。在我们的例子中,第一个规则就是目标 “main” 所在的规则。规则描述了 “main” 的依赖关系,并定义了链接 “.o” 文件生成目标 “main” 的命令;make 在执行这个规则所定义的命令之前,首先处理目标 “main” 的所有的依赖文件(例子中的那些 “.o” 文件)的更新规则(以这些 “.o” 文件为目标的规则)。
对这些 “.o” 文件为目标的规则处理有下列三种情况:
- 目标 “.o” 文件不存在,使用其描述规则创建它;
- 目标 “.o” 文件存在,目标 “.o” 文件所依赖的 “.c” 源文件 “.h” 文件中的任何一个比目标 “.o” 文件“更新”(在上一次 make 之后被修改)。则根据规则重新编译生成它;
- 目标 “.o” 文件存在,目标 “.o” 文件比它的任何一个依赖文件(”.c” 源文件、”.h” 文件)“更新”(它的依赖文件在上一次 make 之后没有被修改),则什么也不做。
通过上面的更新规则我们可以了解到中间文件的作用,也就是我们编译时生成的 “.o” 文件。检查我们当中的某个源文件是不是进行过修改,最终目标文件是不是需要重建。如果存在 “.o” 文件,我们再去执行 make 的时,只有修改过的源文件或者是不存在的目标文件会进行重建,而那些没有改变的文件不用重新编译,这样会在很大程度上节省我们的时间,提高我们的编程效率。小的工程项目我们可能体会不到,项目工程文件越多,这样的效果才越明显。
Makefile中的变量
变量定义
语法格式:
VALUE = One Two Three #### 变量赋值
- 简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
- 递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
- 条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
- 追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
变量使用
使用${VALUE}
或者$(VALUE)
来替换变量值位置.
通配符和自动化变量
shell的通配符在Makefile中通用:
通配符 |使用说明
:—-|:—-
* |匹配0个或者是任意个字符
? |匹配任意一个字符
[] |我们可以指定匹配的字符放在 “[]” 中
特别地,我们在在变量中使用通配符时,必须加上wildcard
前缀否则不会自动展开:
OBJ=$(wildcard *.c)
test:$(OBJ)
gcc -o $@ $^
在上述用到了自动化变量,$@
和$^
,分别代表规则的目标文件和所有依赖的文件列表. 具体所有自动化变量如下:
自动化变量 | 说明 |
---|---|
$@ | 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般称.a 文件为文档文件,也成为静态的库文件),那么它代表这个文档的文件名.在多目标模式规则中,它代表的是触发规则被执行的文件名。 |
$% | 当目标文件是一个静态库文件时,代表静态库的一个成员名。 |
$< | 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。 |
$? | 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。 |
$^ | 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名.一个文件可重复的出现在目标的依赖中,变量$^ 只记录它的第一次引用的情况。就是说变量$^ 会去掉重复的依赖文件。 |
$+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
$* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录部分)。 |
TIPS:
.PHONY
作为目标值可用于如下用途:
- 加快编译效率
- 防止目标名和生成文件冲突 如clean目标可能在会和可执行文件clean冲突.
.PHONY:A B
可用于监控来两个目标文件的生成和更新.
Makefile目录搜索
项目结构如下:
.
├── bin 可执行文件目录
├── include 头文件目录
├── lib 库目录
└── src 源码目录
分为一般搜索VPATH
和选择搜索vpath
(大小写敏感):
- VPATH 是变量,更具体的说是环境变量,Makefile 中的特殊变量,搜索时需要指定文件的路径;
- vpath 是关键字,按照模式搜索,也可以说成是选择搜索。搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件。
我们在 Makefile 中可以这样写:
VPATH := src 我们这可以这样理解,把 src 的值赋值给变量 VPATH,所以我们在执行 make 的时候会,从 src 目录下找我们需要的文件。 当存在多个路径的时候我们可以这样写:
VPATH := src car 或者是
VPATH := src:car 要用空格或者是冒号隔开,这样的话我们就会在这两个路径下搜索文件。搜索的顺序是我们定义的顺序,那上面的例子来说,我们应该先搜索 src 目录底下的文件,再搜索 car 目录下的文件。 >**TIPS:** >无论你定义了多少路径,make 执行的时候会先搜索当前路径下的文件,当前目录下没有我们要找的文件,才去 VPATH 的路径中去寻找。如果当前目录下有我们要使用的文件,那么 make 就会使用我们当前目录下的文件。
vpath 更像是添加了限制条件,会过滤出一部分再去寻找.
具体用法:
vpath PATTERN DIRECTORIES
vpath PATTERN
清除符合文件 PATTERN 的搜索目录vpath
清除清除符合文件 test.c 的搜索目录有已被设置的文件搜索路径( PATTERN:我们可以理解为我们要寻找的条件,DIRECTORIES:我们要寻找的路径 )
示例
- 使用VPATH
VPATH=src include main:main.o list1.o list2.o gcc -o $@ $< main.o:main.c gcc -o $@ $^ list1.o:list1.c list1.h gcc -o $@ $< list2.o:list2.c list2.h gcc -o $@ $<
- 使用vpath
vpath %.c src vpath %.h include main:main.o list1.o list2.o gcc -o $@ $< main.o:main.c gcc -o $@ $^ list1.o:list1.c list1.h gcc -o $@ $< list2.o:list2.c list2.h gcc -o $@ $<
Makefile隐含规则
Makefile 隐含规则:即省去不必要的规则和构建命令.例如下例省去了test.o的依赖和生成规则:
test:test.o gcc -o test test.o
注意:我们的的隐含条件只能省略中间目标文件重建的命令和规则,我们的最终目标的命令和规则不能省略。
- 使用VPATH
隐含规则的具体的工作流程:make 执行过程中找到的隐含规则,提供了此目标的基本依赖关系。确定目标的依赖文件和重建目标需要使用的命令行。隐含规则所提供的依赖文件只是一个基本的(在C语言中,通常他们之间的对应关系是:test.o 对应的是 test.c 文件)。当需要增加这个文件的依赖文件的时候要在 Makefile 中使用没有命令行的规则给出。
当然我们也可以使用 make 选项:-r
或-n-builtin-rules
选项来取消所有的预设值的隐含规则。当然即使是指定了“-r”的参数,某些隐含规则还是会生效。因为有很多的隐含规则都是使用了后缀名的规则来定义的,所以只要隐含规则中含有“后缀列表”那么隐含规则就会生效。
Makefile语法参考
条件语句
libs_for_gcc= -lgnu
normal_libs=
foo:$(objects)
ifeq($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(noemal_libs)
endif
关键字 |功能 :—-:|:—- ifeq |判断参数是否不相等,相等为 true,不相等为 false。 ifneq |判断参数是否不相等,不相等为 true,相等为 false。 ifdef |判断是否有值,有值为 true,没有值为 false。 ifndef |判断是否有值,没有值为 true,有值为 false。
函数调用
$(<function> <arguments>)
${<function> <arguments>}
TODO
特殊目标
名称 |功能 :—-:|:—– .PHONY: |这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。 .SUFFIXES: |这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名 .DEFAULT: |Makefile中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 “.DEFAULT” 所指定的命令。 .PRECIOUS: |这个特殊目标所在的依赖文件在 make 的过程中会被特殊处理:当命令执行的过程中断时,make 不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。 .INTERMEDIATE: |这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。 .SECONDARY: |这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。 .IGNORE |这个目标的依赖文件忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。 .DELETE_ON_ERROR: |如果在 Makefile 中存在特殊的目标 “.DELETE_ON_ERROR” ,make 在执行过程中,荣国规则的命令执行错误,将删除已经被修改的目标文件。 .LOW_RESOLUTION_TIME: |这个目标的依赖文件被 make 认为是低分辨率时间戳文件,给这个目标指定命令是没有意义的。通常的目标都是高分辨率时间戳。 .SILENT: |出现在此目标 “.SILENT” 的依赖文件列表中的文件,make 在创建这些文件时,不打印出此文件所执行的命令。同样,给目标 “SILENT” 指定命令行是没有意义的。 .EXPORT_ALL_VARIABLES: |此目标应该作为一个简单的没有依赖的目标,它的功能是将之后的所有变量传递给子 make 进程。 .NOTPARALLEL: |Makefile 中如果出现这个特殊目标,则所有的命令按照串行的方式执行,即使是存在 make 的命令行参数 “-j” 。但在递归调用的子make进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。
Makefile手动编写
Makefile
Makefile自动生成
一. 前提依赖
编译环境已存在, 因为文中以C语言为例,固安装GCC支持编译.
二. 工作流程示意
三. 实践
main.c
内容如下:
#include <stdio.h>
int main(){
printf("hello linux world !");
return 0;
}