Skip to content
函数与条件判断

函数与条件判断

函数负责「加工变量」,条件判断负责「按环境分支」。这一章是让 Makefile 从「能用」到「灵活」的进阶内容。


函数语法

$(函数名 参数1,参数2,...)
# 或
${函数名 参数1,参数2,...}
  • 函数调用发生在变量展开时,返回一个字符串
  • 参数之间用逗号分隔(参数内可有空格)
  • 函数可嵌套:$(patsubst %.c,%.o,$(wildcard *.c))

常用函数速查

字符串处理

$(subst from,to,text)          # 替换所有 from → to
$(patsubst pattern,replacement,text)  # 按模式替换(% 通配)
$(strip string)                # 去掉首尾及多余空白
$(filter pattern...,text)      # 保留匹配的单词
$(filter-out pattern...,text)  # 剔除匹配的单词
$(sort list)                   # 去重并排序
$(word n,text)                 # 取第 n 个单词
$(words text)                  # 统计单词数
$(firstword text) / $(lastword text)
SRC := main.c util.c test.c
OBJ := $(patsubst %.c,%.o,$(SRC))   # main.o util.o test.o

FILES := $(filter-out test.o,$(OBJ)) # main.o util.o

文件与路径

$(wildcard pattern...)          # 用 shell 通配符匹配文件名(比 shell 的 * 可靠)
$(dir names...)                 # 取目录部分
$(notdir names...)              # 取文件名部分
$(basename names...)            # 去扩展名
$(suffix names...)              # 取扩展名
$(addprefix prefix,names...)    # 每个单词加前缀
$(addsuffix suffix,names...)    # 每个单词加后缀
$(realpath names...)            # 转绝对路径
SRC := $(wildcard ./internal/**/*.go)   # 收集所有 Go 文件
BIN := $(addprefix bin/,$(notdir $(wildcard ./cmd/*)))

执行与条件

$(shell command)               # 执行 shell 命令并返回 stdout
$(if condition,then-part,else-part)
$(or a,b) / $(and a,b)         # 短路求值
GIT_COMMIT := $(shell git rev-parse --short HEAD)
NOW       := $(shell date +%Y%m%d%H%M%S)

TARGET := $(if $(filter linux,$(GOOS)),linux-bin,$(GOOS)-bin)

遍历与模板(foreach / call / eval)

# foreach:对列表每个元素执行,返回结果列表
DIRS := src lib test
BACKUPS := $(foreach d,$(DIRS),$(d)/backup)

# call:调用一个「函数」(用 define 定义的带参数模板)
define run_in_dir
	cd $(1) && $(2)
endef

$(call run_in_dir,src,go test ./...)

# eval:把文本当作 Makefile 语法解析(用于动态生成规则)
define make_target
$(1):
	@echo "Target $(1)"
endef

$(eval $(call make_target,foo))
# 等价于生成了规则 foo:

💡 $(wildcard) 比在 recipe 里用 shell 的 * 更可靠——它在 Make 解析阶段展开,不依赖 shell 环境。


条件判断

条件在 Makefile 解析阶段生效(不是执行阶段),控制「哪些规则被包含」:

ifeq ($(OS),Windows_NT)
	BIN := app.exe
	RM  := del /Q
else
	BIN := app
	RM  := rm -f
endif

四种语法:

ifeq (arg1, arg2)      # arg1 == arg2
ifneq (arg1, arg2)     # arg1 != arg2
ifdef VAR              # VAR 已定义且非空
ifndef VAR             # VAR 未定义或为空

可嵌套,也可用 else

ifdef DEBUG
	CFLAGS += -g
else
	CFLAGS += -O2
endif

🚨 条件是在解析时求值的ifeq 括号里的变量用当时的值。如果后面才改这个变量,条件不会重算。所以条件通常放在 Makefile 靠前位置,并配合 ?= 或命令行传入的变量。


include 引入其它文件

include config.mk
-include optional.mk      # 前加 - 表示文件不存在也不报错
  • include 用于拆分大 Makefile、引入外部配置
  • -include(或 sinclude)容忍文件缺失,常用于引入「可选」的环境覆盖文件
# 常见做法:允许本地覆盖公共配置
-include .env.mk

模式规则与静态模式规则

模式规则(Pattern Rule)

% 通配,一条规则描述「一类文件」的构建方式:

%.o: %.c
	gcc -c $< -o $@
# 任何 x.o 都可由 x.c 编译而来,$< 自动指向对应 .c,$* 是匹配的 x

💡 $*(stem)正是模式规则里 % 匹配到的部分。例如目标 util.o 匹配 %.o,则 $* = util

静态模式规则(Static Pattern Rule)

只对「指定的一组目标」套用模式,而非全局:

OBJ := main.o util.o

$(OBJ): %.o: %.c
	gcc -c $< -o $@
# 只让 main.o、util.o 匹配 %.o: %.c,不影响其它 .o 文件
区别 模式规则 %.o: %.c 静态模式 $(OBJ): %.o: %.c
作用范围 全局所有匹配目标 仅列出的 $(OBJ)
适用场景 通用编译规则 只编译本项目这组文件

文件搜索路径(vpath / VPATH)

当源文件不在当前目录时,告诉 Make 去哪里找依赖:

# 方式一:VPATH 变量(冒号分隔的目录列表)
VPATH = src:../include

# 方式二:vpath 指令(更精细,可按模式指定)
vpath %.c src
vpath %.h include
vpath %.go ./cmd ./internal ./pkg
app: main.go
	go build -o $@ $^
# make 会在 vpath 指定的目录里寻找 main.go