GNU工具链系列(一): Makefile 编译工具
- 概述
- 书写规则
- 执行命令
- 使用变量
- 执行流控制
- 使用函数
- 隐式规则
- make 的运行
- 使用 make 更新函数库文件
1. 概述
主要参考地址: [跟我一起写 makefile | seisman 的博客]
首先,代码的编译过程分为两部分:编译,链接.
编译和链接需要指定编译命令,输入文件,输出文件等,如果文件很多的话,会很麻烦.
而 makefile 就是为了简化编译过程而出现的.
makefile 指定了一系列规则来指导编译和链接的过程.
makefile 的规则形如:
1 | # 注释以 `#` 起始 |
其中目标可以是一个具体文件,也可以是一个动作,如clean
动作
在使用 makefile 的时候,可以主动指定要生成的目标
1 | # 主动声明哪些目标是动作而不是文件 |
也可以直接运行 make 命令,使用默认的第一个目标
make 会自动递归寻找依赖目标并执行其命令,同时,makefile 会自动对比 target 文件和 prerequisite 文件的更新日期,如果目标依赖文件没有更新,则其会自动跳过该命令,使用上一次生成的目标
makefile 中可以定义变量,使用方法与 shell 中类似,同时 makefile 中还定义了一些预定义变量
1 | # 定义变量 |
使用include
关键字引用其他 makefile(被引用文件会被原样复制到引用位置)
可以在执行 make 命令的时候使用-I
参数指出引用目录
1 | # 普通引用 |
总的工作流程:
- 读取所有的 makefile
- 执行
include
命令- 初始化变量
- 推到隐式规则
- 为所有目标建立依赖关系链
- 决定哪些目标需要重新生成
- 执行生成命令
2. 书写规则
2.1 基本规则
首先基本的书写规则为:
1 | target : prerequisite |
2.2 文件搜索
在项目比较大的时候,可以设置 makefile 的搜索路径,即环境变量VPATH
同时,当前目录为最高优先级,其次为 VPATH 中的路径
1 | # 路径优先级从头到尾递降,不同路径以冒号分割 |
除了使用变量外,可以使用关键字vpath
指定路径
1 | # 为符合 pattern 的文件指定路径 directory |
2.3 伪目标
并非所有的目标都对应一个文件,有的目标可能对应一个动作
1 | # 使用 .PHONY 指明哪些目标是伪目标(动作) |
2.4 多目标
有时候多个目标文件会依赖同一套依赖,同时命令也类似
此时可以使用多目标的方法简化操作
1 | # 以下规则会自动展开成下面两个规则 |
2.5 静态模式
静态模式定义了一系列输入文件和输出文件类似时的处理规则
1 | # targets 为待处理的一系列输出文件,可以引用变量 |
2.6 自动生成依赖性
具体参考:[makefile 自动生成依赖 | seisman 的博客]
编译的时候可以使用-M/-MM 选项来仅仅生成依赖目标
1 | # 使用-MM选项会忽略标准库文件 |
3. 执行命令
3.1 执行命令
makefile 中执行命令或执行 make 时有一些可以操作的选项
1 | # 在命令中,可以加上#前缀来取消输出命令 |
1 | # 执行make命令的时候 |
makefile 中的命令会一条一条执行,在执行下一条指令的时候会恢复初始状态
(仅限更改了当前终端的命令如 cd,更改系统状态的命令除外如 mount)
如果希望把上一个命令的结果应用于下一条指令,可以把两条指令写在一行并用分号分隔
1 | target: |
正常情况下,如果 makefile 中的命令执行出错,会终止规则的运行
如果希望忽略命令的错误,可以在命令前添加一个-
1 | clean: |
3.2 传递参数
makefile 可以实现分层设计(各个文件夹内的 makefile 控制本文件夹的构建行为)
1 | # 显式声明把变量传递给下一级makefile |
3.3 定义命令包
可以使用定义命令包的方式来为所有的重复命令序列定义一个模板
1 | # 定义一个名字为 compile-lib 的命令包 |
4. 使用变量
4.1 变量的声明
makefile 中有四种变量声明方式
赋值方式 | 作用 |
---|---|
简单赋值( := ) | 编程语言中常规理解的赋值方式,只对当前语句的变量有效. |
递归赋值( = ) | 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响. |
条件赋值( ?= ) | 如果变量未定义,则使用符号中的值.如果该变量已经赋值则无效. |
追加赋值( += ) | 原变量用空格隔开的方式追加一个新值. |
4.2 变量的高级用法
变量替换
1
2
3
4
5
6
7
8
9# 第一种方法,所有被匹配到的地方会被替换
# ao bo co
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# 第二种方法,使用模式进行匹配
# x123y x234y xjkhy
src := a123b.c a234b.c ajkhb.c
obj := $(src:a%b.c=x%y)变量寻址
1
2
3
4
5
6# 可以把一个变量的值当做地址进行寻址
# 最后的结果是 a=u
x = y
y = z
z = u
a := $($($(x)))变量进行组合(可以和变量寻址组合使用)
1
2
3
4
5# 最后的结果为all=Hello
first_second = Hello
a = first
b = second
all = $($(a)_$(b))使用命令行设置参数
1
2
3
4
5
6
7
8
9all=123
target:
@echo ${all}
# 命令行中可以传入参数(会覆盖同名变量,但不同实现也可能不覆盖)
make all=456
# 可以使用 override 关键字主动指定该变量不被覆盖
override all=123
target:
@echo ${all}多行变量
1
2
3
4
5# 可以使用 define 关键字定义多行变量(和命令包是同一个关键字)
define val
aoo.c
boo.c
endef目标变量(即这个变量仅对一个目标生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# CFLAGS 仅对prog这个目标生效
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
# 通用格式如下,同时对于目标可以使用模式进行匹配
<target ...> : <variable-assignment>;
<target ...> : overide <variable-assignment>5. 执行流控制
1 | # 通用语法如下 |
6. 使用函数
makefile 中使用函数的通用格式如下,其中参数可以有多个,以逗号分隔
1 | $(<function> <arguments>) |
6.1 字符串处理函数
1 | # 1. 字符串替换 |
6.2 文件名操作函数
1 | # 1. 取目录函数 |
6.3 其他函数
1 | # 1. foreach函数 |
7. 隐式规则
7.1 隐式规则推导
仅关注 C 和 C++的隐式推导
编译 C 程序的隐含规则
\
.o 的目标的依赖目标会自动推导为 \ .c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
编译 C++程序的隐含规则
\
.o 的目标的依赖目标会自动推导为 \ .cc 或是 \ .C ,并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CFLAGS)
建议使用 .cc 作为 C++源文件的后缀,而不是 .C
7.2 默认变量
默认程序变量
| 变量 | 默认值 | 含义 |
| :—- | :——- | :———————- |
| AS | as | 汇编语言编译程序 |
| CC | cc | C 语言编译程序 |
| CXX | g++ | C++编译程序 |
| RM | rm -f | 删除文件命令 |
| AR | ar | 函数库打包程序 |默认参数变量
| 变量 | 含义 |
| :———- | :————————- |
| ASFLAGS | 汇编语言编译器参数 |
| CFLAGS | C 语言编译器参数 |
| CXXFLAGS | C++语言编译器参数 |
| CPPFLAGS | C 预处理器参数 |
7.3 自动化变量
变量 | 含义 |
---|---|
$@ | 目标文件集合 |
$% | 目标中的函数库文件 |
$< | 依赖中的第一个依赖文件名 |
$? | 所有比目标新的依赖目标的集合 |
$^ | 所有的依赖目标的集合(去重) |
$+ | 所有的依赖目标的集合(不去重) |
可以给上述变量加上D
或F
以表示取路径或文件
1 | $(@D):取目标文件的路径 |
8. make 的运行
主要是命令行中的 make 参数设置
选项 | 含义 |
---|---|
-n | 不执行命令只打印,用于调试 makefile |
-s | 只执行命令不打印 |
-t | 只把目标文件时间更新而不实际重新生成 |
-q | 查询目标是否需要更新(0->不需要;1->需要;2->目标错误) |
-b | 忽略和其他版本 make 的兼容性 |
-B | 忽略目标文件时间差异,编译所有需要的文件 |
-C | 指定 makefile 的文件夹 |
-debug=[options] | 指定输出信息等级(a,v,b) |
-d | -debug=d 的缩写 |
-f=[file] | 指定 makefile |
-i | 忽略所有错误 |
-I | 添加 makefile 搜索目录(可以叠加) |
-j [N] | 指定同时运行的命令个数(没有这个选项允许无限个) |
-r | 禁止使用所有隐式规则 |
-R | 仅是使用作用于变量的隐式规则 |
9. 使用 make 更新函数库文件
(略,从来没用过,以后用到再学)