用途
本技能引导智能体掌握 C/C++ 项目中符合惯例的 Makefile 写法,包括伪目标、模式规则、自动依赖生成以及常见构建惯例。
触发场景
- “如何为我的 C 项目编写 Makefile?”
- “我的 Makefile 每次都重新构建所有内容”
- “如何为 Make 添加依赖跟踪?”
- “$@、$<、$^ 分别是什么意思?”
- “我遇到了 'make: Nothing to be done for all'”
- “如何把我的 shell 编译脚本转换成 Makefile?”
主要能力
1. C 语言的最小正确 Makefile
CC := gcc
CFLAGS := -std=c11 -Wall -Wextra -g -O2
LDFLAGS :=
LDLIBS :=
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:src/%.c=build/%.o)
TARGET := build/prog
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
build/%.o: src/%.c | build
$(CC) $(CFLAGS) -c -o $@ $<
build:
mkdir -p build
clean:
rm -rf build
自动变量:
$@— 目标名$<— 第一个前置依赖$^— 所有前置依赖(去重)$*— 词干(模式规则中 % 匹配的部分)$(@D)—$@的目录部分
2. 自动依赖生成
若不使用此机制,修改头文件不会触发包含它的 .c 文件重新构建。
CC := gcc
CFLAGS := -std=c11 -Wall -Wextra -g -O2
DEPFLAGS = -MMD -MP # -MMD: 生成 .d 文件; -MP: 为头文件生成伪目标
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:src/%.c=build/%.o)
DEPS := $(OBJS:.o=.d)
TARGET := build/prog
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
build/%.o: src/%.c | build
$(CC) $(CFLAGS) $(DEPFLAGS) -MF $(@:.o=.d) -c -o $@ $<
-include $(DEPS) # '-' 在首次构建时忽略错误(此时还没有 .d 文件)
build:
mkdir -p build
clean:
rm -rf build
3. 模式规则速查
# 编译 C
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# 编译 C++
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $<
# 生成汇编
%.s: %.c
$(CC) $(CFLAGS) -S -o $@ $<
# 对每个文件运行工具
build/%.processed: src/%.raw | build
mytool $< > $@
4. 常见 Make 模式
调试与发布构建:
BUILD ?= release
ifeq ($(BUILD),debug)
CFLAGS += -g -Og -DDEBUG
else
CFLAGS += -O2 -DNDEBUG
endif
用法:make BUILD=debug
并行构建:
make -j$(nproc) # 使用所有 CPU
make -j4 # 恰好 4 个任务
添加 -Otarget(或 -O)可让输出有序:make -j$(nproc) -O
详细输出:
# 在 Makefile 中:用 @ 抑制回显
build/%.o: src/%.c | build
@echo " CC $<"
@$(CC) $(CFLAGS) -c -o $@ $<
用 $(V) 做守卫时,可通过 make V=1 覆盖静默:
Q := $(if $(V),,@)
build/%.o: src/%.c
$(Q)$(CC) $(CFLAGS) -c -o $@ $<
安装:
PREFIX ?= /usr/local
install: $(TARGET)
install -d $(DESTDIR)$(PREFIX)/bin
install -m 0755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/
5. 多目录项目
对于中等规模项目,避免递归 make(脆弱且慢)。使用一个扁平 Makefile 包含子 Makefile:
# project/Makefile
include lib/module.mk
include src/app.mk
# lib/module.mk
LIB_SRCS := $(wildcard lib/*.c)
LIB_OBJS := $(LIB_SRCS:lib/%.c=build/lib_%.o)
OBJS += $(LIB_OBJS)
build/lib_%.o: lib/%.c
$(CC) $(CFLAGS) -c -o $@ $<
6. 常见错误
| 错误 | 原因 | 修复 |
|---|---|---|
| No rule to make target 'foo.o' | 缺少源文件或规则 | 检查源路径和模式规则 |
| Nothing to be done for 'all' | 目标已是最新 | 触碰源文件或运行 make clean |
| Circular dependency dropped | 目标依赖自身 | 检查依赖链 |
| missing separator | 制表符与空格混用 | 命令必须使用制表符,而非空格 |
| *** multiple target patterns | 模式规则语法错误 | 检查 % 的位置 |
| 每次都全量重建 | 时间戳错误,或缺少 PHONY | 检查日期;确保 all 是 .PHONY |
| 头文件变更未被检测 | 没有依赖跟踪 | 添加 -MMD -MP 和 -include $(DEPS) |
完整的变量与函数参考见 references/cheatsheet.md。
相关技能
- CMake 项目请使用 skills/build-systems/cmake
- 将 Ninja 作为 make 后端请使用 skills/build-systems/ninja
- CFLAGS 细节请使用 skills/compilers/gcc