Makefile 命令速查表
Makefile 是 C/C++ 项目和通用自动化构建的核心工具。GNU Make 通过 Makefile 定义目标、依赖和命令规则,实现增量构建和任务自动化。本速查表覆盖了编写 Makefile 最常用的语法和技巧,从基础变量和规则到高级函数和条件判断,每个示例都来自实际开发场景。
快速参考
| 你要做什么 | 用哪个语法 |
| 定义一个变量 | CC := gcc |
| 引用一个变量 | $(CC) -o $@ $< |
| 写一个编译规则 | %.o: %.c ; $(CC) -c $< -o $@ |
| 声明伪目标 | .PHONY: clean |
| 查找所有 .c 文件 | $(wildcard src/*.c) |
| 模式替换文件名 | $(patsubst %.c,%.o,$(wildcard *.c)) |
| 条件判断 | ifeq ($(OS),Linux) |
变量定义
``makefile
# 赋值方式
CC = gcc # 递归展开赋值(延迟求值)
CC := gcc # 简单展开赋值(立即求值,推荐)
CC ?= gcc # 条件赋值(仅当未定义时)
CFLAGS += -Wall -O2 # 追加赋值
override CFLAGS += -g # 强制覆盖(即使命令行指定)
# 变量引用 $(CC) -o hello hello.c # 标准引用 ${CC} -o hello hello.c # 等价引用(花括号) $@ # 自动变量:当前目标名 $< # 自动变量:第一个依赖 $^ # 自动变量:所有依赖(去重) $+ # 自动变量:所有依赖(不去重) $* # 自动变量:模式匹配的 stem $? # 自动变量:比目标新的依赖
# 多行与 Shell 变量 define SCRIPT @echo "Running..." @echo "Done." endef
PATH := $(PATH):/usr/local/bin # 引用环境变量
SHELL := /bin/bash # 指定 shell(GNU make 默认 /bin/sh)
`
目标规则
`makefile
# 基本规则
target: prerequisites
recipe # 注意:必须用 Tab 缩进,不能用空格
# 多依赖规则 app: main.o utils.o $(CC) -o $@ $^ @echo "Build complete: $@" # @ 前缀抑制命令回显
# 模式规则(最常用) %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
%.o: %.c %.h # 带头文件依赖的模式规则 $(CC) -c $(CFLAGS) $< -o $@
# 多目标规则 obj1.o obj2.o: common.h # 多个目标依赖同一个文件
# 静态模式规则 objects = main.o utils.o $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
# 双冒号规则(每次独立执行) clean:: rm -f *.o clean:: rm -f app # 多个 recipe,每个都执行
# 隐式规则链
# make 自动知道如何从 .c 构建 .o(使用内置规则)
`
函数使用
`makefile
# 文件匹配
SOURCES := $(wildcard src/*.c) # 匹配所有 .c 文件
OBJECTS := $(patsubst %.c,%.o,$(SOURCES)) # 将 .c 替换为 .o
HEADERS := $(wildcard include/*.h) # 匹配头文件
OBJECTS := $(SOURCES:.c=.o) # 简写模式替换
# 字符串操作 FILES := foo.c bar.c baz.h $(filter %.c,$(FILES)) # 过滤出 .c 文件 $(filter-out %.h,$(FILES)) # 排除 .h 文件 $(basename foo.c) # 取文件名(不含扩展名) $(suffix foo.c) # 取扩展名 $(notdir src/main.c) # 取文件名部分 $(dir src/main.c) # 取目录部分
# 列表操作 $(addprefix obj/,$(OBJECTS)) # 给每个文件加前缀 $(addsuffix .bak,$(FILES)) # 给每个文件加后缀 $(words $(FILES)) # 统计单词数 $(word 2,$(FILES)) # 取第 2 个单词 $(firstword $(FILES)) # 取第一个单词 $(lastword $(FILES)) # 取最后一个单词
# 循环与执行 $(foreach src,$(SOURCES),$(info Processing $(src))) # 遍历处理
TAG := $(shell git describe --tags --always) # 执行 shell 命令
DATE := $(shell date +%Y-%m-%d) # 获取当前日期
$(shell mkdir -p build) # 创建目录(解析时执行)
`
条件判断
`makefile
# 变量值判断
ifeq ($(CC),gcc)
CFLAGS += -std=c11
endif
ifneq ($(shell uname),Linux) CFLAGS += -D_POSIX_C_SOURCE=200809L endif
# 变量定义判断 ifdef DEBUG CFLAGS += -g -O0 -DDEBUG LDFLAGS += -fsanitize=address endif
ifndef RELEASE CFLAGS += -Wall -Wextra endif
# 多重条件 ifeq ($(OS),Windows_NT) RM := del /Q EXT := .exe else ifeq ($(shell uname),Darwin) RM := rm -f EXT := else RM := rm -f EXT := endif
# $(if) 函数(在变量展开时求值)
CFLAGS := $(if $(DEBUG),-g -O0,-O2) # 条件选择值
LIBDIR := $(if $(wildcard /usr/local/lib),/usr/local/lib,/usr/lib)
`
内置变量与隐式规则
`makefile
# 常用内置变量
CC := cc # C 编译器
CXX := g++ # C++ 编译器
AR := ar # 归档器
CFLAGS := # C 编译器标志
CXXFLAGS := # C++ 编译器标志
LDFLAGS := # 链接器标志
LDLIBS := # 链接库(如 -lm)
CPPFLAGS := # 预处理器标志(如 -Iinclude)
RM := rm -f # 删除命令
# 内置隐式规则示例 # %.o: %.c — 使用 $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< # %.o: %.cc — 使用 $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< # %: %.c — 使用 $(CC) $(LDFLAGS) $< $(LDLIBS) -o $@ # %: %.o — 链接 .o 文件
# 覆盖隐式规则变量 .SUFFIXES: # 清空后缀列表 .SUFFIXES: .c .o .h # 只保留需要的后缀
# 特殊变量
$(MAKE) # make 自身(用于递归 make)
$(MAKECMDGOALS) # 当前命令行目标
$(CURDIR) # 当前工作目录
$(MAKEFLAGS) # 当前 make 标志
$(.VARIABLES) # 所有已定义变量列表
$(.TARGETS) # 所有目标列表
`
伪目标与最佳实践
`makefile
# 声明伪目标
.PHONY: all clean install test help lint dist
# 默认目标(放在第一个) all: app test # make 不带参数时执行
# 清理 clean: $(RM) $(OBJECTS) $(TARGET) $(RM) -r build/
# 安装 install: app @mkdir -p $(DESTDIR)/usr/local/bin cp $< $(DESTDIR)/usr/local/bin/ @echo "Installed to $(DESTDIR)/usr/local/bin/"
# 测试 test: app ./app --test @echo "All tests passed!"
# 帮助 help: @echo "Available targets:" @echo " all Build the project (default)" @echo " clean Remove build artifacts" @echo " install Install to \$$(DESTDIR)/usr/local/bin" @echo " test Run tests" @echo " lint Run code linting" @echo " dist Create distribution archive"
# 代码检查 lint: $(info Running linter...) $(shell command -v cppcheck >/dev/null || (echo "cppcheck not found" && exit 1)) cppcheck --enable=all $(SOURCES)
# 分发包 dist: $(info Creating distribution...) tar -czf $(TARGET)-$(TAG).tar.gz $(SOURCES) $(HEADERS) Makefile
# 最佳实践 Makefile 模板 CURRENT_DIR := $(notdir $(CURDIR)) TARGET := $(CURRENT_DIR) TAG := $(shell git describe --tags --always 2>/dev/null || echo "dev")
SOURCES := $(wildcard src/*.c) OBJECTS := $(SOURCES:.c=.o) HEADERS := $(wildcard include/*.h) CFLAGS := -Wall -Wextra -std=c11 -Iinclude LDFLAGS := LDLIBS := -lm
$(TARGET): $(OBJECTS) $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.c $(HEADERS) $(CC) -c $(CFLAGS) -o $@ $<
.PHONY: all clean install test help all: $(TARGET) clean: ; $(RM) $(OBJECTS) $(TARGET) ``
变量定义(7)
| 命令 | 难度 | ||
|---|---|---|---|
VAR := value简单展开变量赋值(立即求值,推荐使用) | 基础 | CC := gcc | |
VAR = value递归展开变量赋值(延迟求值) | 基础 | CC = gcc | |
VAR ?= value条件赋值(仅在变量未定义时赋值) | 中级 | CC ?= gcc | |
VAR += value追加赋值到已有变量 | 基础 | CFLAGS += -Wall -O2 | |
override VAR += value强制覆盖变量(即使命令行指定了该变量) | 中级 | override CFLAGS += -g | |
$(VAR) or ${VAR}引用变量的值 | 基础 | $(CC) -o app main.c | |
define VAR ... endef定义多行变量(可用于复杂的 recipe) | 中级 | define BUILD_SCRIPT
@echo "Building..."
$(CC) -o $@ $^
endef |
目标规则(6)
| 命令 | 难度 | ||
|---|---|---|---|
target: prerequisites
recipe基本目标规则(依赖和命令) | 基础 | app: main.c
gcc -o app main.c | |
app: main.o utils.o
$(CC) -o $@ $^带多个依赖的编译规则 | 基础 | app: main.o utils.o
$(CC) -o $@ $^ | |
%.o: %.c
$(CC) -c $< -o $@模式规则——将 .c 文件编译为 .o 文件 | 中级 | %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@ | |
$(objects): %.o: %.c
$(CC) -c $< -o $@静态模式规则,仅对指定文件列表应用模式 | 中级 | objects = main.o utils.o
$(objects): %.o: %.c
$(CC) -c $< -o $@ | |
target:: prerequisites双冒号规则(每个 recipe 都独立执行) | 高级 | clean:: rm -f *.o
clean:: rm -f app | |
@echo 'Building...'静默执行命令(@ 前缀抑制命令回显) | 基础 | @echo 'Build complete: $(TARGET)' |
函数使用(7)
| 命令 | 难度 | ||
|---|---|---|---|
$(wildcard pattern)匹配实际存在的文件列表 | 基础 | $(wildcard src/*.c) | |
$(patsubst %.c,%.o,$(SOURCES))模式字符串替换(将 .c 替换为 .o) | 中级 | $(patsubst %.c,%.o,$(wildcard *.c)) | |
$(shell command)执行 shell 命令并返回输出 | 中级 | $(shell git rev-parse HEAD) | |
$(filter pattern,text)从列表中过滤出匹配模式的文件 | 中级 | $(filter %.c,$(FILES)) | |
$(foreach var,list,text)遍历列表并对每一项执行文本扩展 | 中级 | $(foreach f,$(SOURCES),$(info Processing $(f))) | |
$(addprefix prefix,names)给列表中的每个文件添加前缀路径 | 中级 | $(addprefix obj/,$(OBJECTS)) | |
$(notdir path)从文件路径中提取文件名部分 | 基础 | $(notdir src/main.c) |
条件判断(6)
| 命令 | 难度 | ||
|---|---|---|---|
ifeq (arg1, arg2) ... endif条件判断——比较两个值是否相等 | 基础 | ifeq ($(CC),gcc)
CFLAGS += -std=c11
endif | |
ifneq (arg1, arg2) ... endif条件判断——比较两个值是否不相等 | 基础 | ifneq ($(shell uname),Linux)
CFLAGS += -D_POSIX_C_SOURCE
endif | |
ifdef VAR ... endif检查变量是否已定义 | 基础 | ifdef DEBUG
CFLAGS += -g -O0
endif | |
ifndef VAR ... endif检查变量是否未定义 | 基础 | ifndef RELEASE
CFLAGS += -Wall -Wextra
endif | |
ifeq ... else ifeq ... else ... endif多重条件分支判断 | 中级 | ifeq ($(OS),Windows_NT)
RM := del /Q
else ifeq ($(shell uname),Darwin)
RM := rm -f
else
RM := rm -f
endif | |
$(if condition,then-part,else-part)$(if) 函数——在变量展开时做条件选择 | 中级 | CFLAGS := $(if $(DEBUG),-g -O0,-O2) |
内置变量与隐式规则(6)
| 命令 | 难度 | ||
|---|---|---|---|
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS)常用的内置编译相关变量 | 基础 | $(CC) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@ | |
$(MAKE)make 自身的变量(用于递归调用 make) | 中级 | $(MAKE) -C subdir | |
$(MAKECMDGOALS)当前命令行中指定的目标列表 | 中级 | $(info Building goals: $(MAKECMDGOALS)) | |
$(CURDIR)当前工作目录的绝对路径 | 基础 | OUTPUT := $(CURDIR)/build | |
$(.VARIABLES)所有已定义的变量列表(用于调试) | 高级 | $(warning Defined variables: $(.VARIABLES)) | |
$(MAKEFLAGS)当前 make 的 flags 变量 | 中级 | $(info Make flags: $(MAKEFLAGS)) |
伪目标与最佳实践(8)
| 命令 | 难度 | ||
|---|---|---|---|
.PHONY: target1 target2声明伪目标(不表示实际文件) | 基础 | .PHONY: all clean install test help | |
clean:
rm -f *.o $(TARGET)清理编译产物的 clean 目标 | 基础 | clean:
$(RM) $(OBJECTS) $(TARGET) | |
all: target1 target2默认目标(make 不带参数时执行的第一个目标) | 基础 | all: app test | |
install: app
cp $< $(DESTDIR)/usr/local/bin/安装目标——将构建产物复制到系统目录 | 中级 | install: app
@mkdir -p $(DESTDIR)/usr/local/bin
cp $< $(DESTDIR)/usr/local/bin/ | |
test: app
./app --test测试目标——运行测试用例 | 基础 | test: app
./app --test
@echo "All tests passed!" | |
help:
@echo 'Available targets...'帮助目标——列出所有可用的目标及说明 | 中级 | help:
@echo 'Available targets:'
@echo ' all Build the project'
@echo ' clean Remove artifacts'
@echo ' test Run tests' | |
$(info message)在解析时打印调试信息 | 中级 | $(info Building $(TARGET) version $(TAG)) | |
$(error message)在解析时打印错误信息并终止构建 | 中级 | $(error Unsupported platform: $(shell uname)) |