变量与自动变量
变量让 Makefile 可配置、可复用。理解赋值方式和展开时机是写好 Makefile 的关键,也是最容易踩坑的地方。
四种赋值方式
A = foo # 递归展开(延迟求值)
B := foo # 简单展开(立即求值)
C ?= foo # 若未定义才赋值
D += bar # 追加
| 运算符 | 名称 | 求值时机 | 典型用途 |
|---|---|---|---|
= |
递归展开 | 使用时才展开 | 引用后面才定义的变量 |
:= |
简单展开 | 定义时立即展开 | 绝大多数场景,行为可预期 |
?= |
条件赋值 | 未定义时才赋值 | 给变量提供默认值,允许外部覆盖 |
+= |
追加 | 取决于原变量的赋值方式 | 累加 flags、路径 |
递归展开 vs 简单展开(核心陷阱)
# 递归展开(=):使用时才求值
X = $(Y)
Y = hello
# $(X) 最终展开为 hello —— 因为 Y 在使用时已经定义
# 简单展开(:=):定义时立即求值
A := $(B) # 此时 B 还未定义,$(A) 展开为空
B := world
# $(A) 是空字符串,而不是 world
# 递归展开的经典翻车案例
CC = gcc
CFLAGS = $(CFLAGS) -O2 # 🚨 自我引用 → 无限递归,报 "Recursive variable ... references itself"
🚨 能用
:=就尽量用:=:简单展开行为确定、无副作用、无递归风险。只有需要「引用后面才定义的值」时才用=。
自动变量
自动变量在 recipe 中自动取「当前规则」的值,是让规则可复用的核心:
app: main.go util.go
go build -o $@ $^
| 变量 | 含义 | 上述规则的展开值 |
|---|---|---|
$@ |
目标名 | app |
$< |
第一个依赖 | main.go |
$^ |
所有依赖(去重) | main.go util.go |
$+ |
所有依赖(不去重,保留顺序) | main.go util.go |
$? |
比目标新的依赖 | 仅更新过的那个 |
$* |
模式规则匹配的 stem | 见 03 模式规则 |
$(@D) / $(@F) |
目标的目录部分 / 文件名部分 | ./ / app |
$(<D) / $(<F) |
第一个依赖的目录 / 文件名 | ./ / main.go |
💡 记忆技巧:
$@(target 就像射击的靶心 @);$<(第一个依赖,像"小于"符号指向第一个);$^(所有依赖,像"上箭头"盖住所有)。
内置变量
Make 预定义了一些变量,可覆盖其默认值:
| 变量 | 默认值 | 说明 |
|---|---|---|
CC |
cc |
C 编译器 |
CXX |
g++ |
C++ 编译器 |
CFLAGS / CXXFLAGS |
空 | 编译选项 |
RM |
rm -f |
删除命令 |
SHELL |
/bin/sh |
recipe 使用的 shell |
MAKE |
make |
当前 make 程序(递归 make 用) |
MAKEFLAGS |
— | 传给子 make 的标志 |
MAKECMDGOALS |
— | 命令行指定的目标列表 |
# 自定义
SHELL := /bin/bash
CC := gcc
CFLAGS := -Wall -O2
变量的优先级(覆盖顺序)
同一个变量可能来自多个来源,优先级从高到低:
1. make 命令行参数(make VAR=value)
2. Makefile 内用 override 声明
3. Makefile 内普通赋值
4. 环境变量(默认导入)
5. 内置默认值make build VERSION=2.0.0 # 命令行最高优先级,可覆盖 Makefile# 若希望 Makefile 的赋值反过来压过命令行,用 override
override CFLAGS += -g
🚨 环境变量会自动变成 make 变量:
export过的变量在 Makefile 里可直接$(VAR)引用。这方便传值,但也意味着环境里若恰好有同名变量会「悄悄」覆盖——排查变量值时留意环境。
导出变量给 recipe
Makefile 里的变量默认不会自动传给 recipe 中的子进程,需要 export:
VERSION := 1.0.0
export VERSION # recipe 里的脚本能读到 $VERSION
# 或临时导出
build:
VERSION=$(VERSION) go build -ldflags "-X main.version=$(VERSION)" ./...
多行变量(define)
用 define ... endef 定义多行内容,适合装一段脚本或长命令:
define print_help
@echo "可用目标:"
@echo " build 构建项目"
@echo " test 运行测试"
endef
help:
$(print_help)
💡
define定义的是「文本块」,可当变量用($(print_help)直接展开成多行命令),也可配合$(eval)/$(call)生成规则,见03。