Skip to content
变量与自动变量

变量与自动变量

变量让 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