0 Preface/Forword
0.1 makefile文件定义变量的方法
在Makefile中,定义变量的方法:
- 直接在makefile文件内部定义
- 在make命令行中定义
1 变量定义
1.1 make命令中定义
在makefile中,通过命令定行定义或修改变量是一种非常灵活且常用的方式,主要用于动态控制构建行为(切换编译模式、指定安装路径等)。
1.1.1 语法规则
基本语法:
在执行make命令时,直接在命令后添加var_name=value就可以。如果有多个变量,可以用空格分隔。
1.1.2 实例
源码:
var_print: @echo "compiler: ${CC}" @echo "compiler: $(CC)"运行结果:
cc和gcc:
1.1.3 命令行定义命令常用场景
| 场景 | 命令示例 | 说明 |
| 切换编译器 | make CC=g++ | 临时更换C/C++编译器 |
| 开启调试 | make CFLAGS="-g -O0" | 添加调试符号,关闭优化 |
| 指定安装路径 | make PREFIX=/usr/local install | 自动以软件安装路径 |
| 选择构建目标 | make BUILD_TYPE=release all | 根据变量条件编译不同代码分支 |
1.2 ?= 定义变量 (Conditional assignment)
在makefile中,通过?=来定义变量的值。
处理规则:赋值前,判断该变量是否已经存在,如果存在,则不重新赋值;如果不存在,则重新赋值。(用于给变量赋默认值)
展开方式:与=赋值方式一样,递归展开
以下三种情况?=不会赋值:
- 命令行定义了变量。
- makefile中前面已经定义了变量
- 环境变量中已经定义了该变量:比如CC变量。
1.2.1 Example
CC ?= g++ MODE ?= debug var_print: @echo "compiler: $(CC)" @echo "mode: $(MODE)"运行结果:
1.3 := 定义变量 (Simple Expansion)
通过:=方式定义的变量称为简单展开变量(或者称为立即展开变量)。
相关属性如下:
- 规则
- 立即求值:Make在读取该行时,会立即计算右边表达式的值,并将结果固定下来。
- 静态值:一旦赋值完成,变量的值就确定了,后续对右侧依赖变量的修改不会影响改变量。
- 只能引用已经定义过的变量:右侧只能引用在此之前已经定义过的变量,否则值为空。
- 优点:效率高(只计算一次),行为可预测,避免循环引用问题。
- 缺点:不能引用后面定义的变量。
1.4 += 定义变量 (Appended)
+=用于向变量的末尾添加内容,常用于累积源文件列表或编译标志。
具体属性如下所示:
- 规则:
- 自动空格:make会在原有值和新增值之间自动添加一个空格。
- 继承类型:+=的行为取决于该变量之前是如何定义的。
- 如果变量之前是用=定义的,+=保持递归展开特性。
- 如果变量之前是用:=定义的,+=保持简单展开特性。(先展开旧值,再追加新值)
- 如果变量之前未定义,+=等效于=,将其定义为递归展开变量。
- 典型场景:模块化构建中,不同模块向同一个变量(如SRCS或CFLAGS)添加内容。
1.5 = 定义变量 (Recursive Expansion)
makefile中最基本的赋值方式,定义的变量称之为递归展开变量。
相关属性如下:
- 规则:
- 延时求值:make在读取该行时不会立即计算右边的值,而是将右边的文本原样保存。
- 使用时展开:只有当该变量在后续被引用($(VAR))时,make才会去展开其中的内容。
- 引用最新值:展开时,它会使用变量在makefile中最后一次被赋予的值。意味着可以引用在定义之后才赋值的变量。
- 优点:灵活,允许变量引用未来定义的变量。
- 缺点:如果存在循环引用(如A=$(B), B=$(A)),会导致无限递归错误;每次引用都重新计算,性能较低。
1.6 变量传递给子make (sub-make)
在一个makefile中调用另一个make命令(嵌套make)时;命令行定义的变量会自动传递给子make进程。
define_var.mk源码:
CC ?= g++ MODE ?= debug var_print: @echo "compiler: $(CC)" @echo "mode: $(MODE)" make -f sub_test.mksub_test.mk源码:
MODE ?= debug all: @echo "mode: $(MODE)"运行结果:
1.7 override的作用
override标记的变量,不会被命令行中的定义所覆盖。
CC ?= g++ override MODE = debug var_print: @echo "compiler: $(CC)" @echo "mode: $(MODE)" make -f sub_test.mk1.8 变量定义对比总结
总结表如下所示:
| 操作符 | 名称 | 求值时机 | 能否引用后定义变量 | 是否覆盖已有值 | 典型用途 |
| = | 递归赋值 | 使用时(延时) | ✅️能 | ✅️是 | 通用变量定义,灵活性强。 |
| := | 简单赋值 | 定义时(立即) | ❌️不能 | ✅️是 | 性能敏感场景,避免副作用(循环引用)。 |
| ?= | 条件赋值 | 使用时(延时) | ✅️能 | ❌️否(若已有值) | 设置默认值,允许命令行覆盖。 |
| += | 追加赋值 | 取决于前文 | 取决于前文 | ❌️追加 | 积累列表(如源文件、标志) |
1.9 变量定义最佳实践
定义变量相关建议:
- 优先使用 :=:除非需要引用后面定义的变量,否则强烈推荐使用 := ,因为它效率更高且行为直观。
- 使用 ?= 设置默认值:对于编译器路径、优化级别等配置项,使用 ?= 可以让用户更容易通过命令行自定义。
- 使用 += 管理列表:在大型项目中,使用 += 逐步构建源文件列表或编译参数,可以提供makefile的可维护性。
- 避免循环引用:使用 = 时要小心不要形成 A=$(B) 和 B=$(A)这样的循环。
2 变量引用
引用变量的两种方法:
- $(VAR)
- ${VAR}
var_print: @echo "compiler: ${CC}" @echo "compiler: $(CC)"3 变量优先级
3.1 变量的来源
Make变量的来源只要有四个渠道,优先级从高到底依次为:
- 命令行参数(最高优先级):通过命令行make VAR=value传递。
- makefile中override定义:使用override VAR=value定义变量。
- makefile中普通定义:使用=、:=、?=、+=定义变量。
- 环境变量(最低优先级):系统环境中已经存在的变量(除非使用-e选项)