通过 4 个渐进关卡,从理解基本概念到独立编写复杂构建脚本。每一步都有交互式演示和即时反馈。
Makefile 的知识体系由 8 个核心概念组成。点击卡片展开详情,了解它们之间的关系。
规则左侧的名字,代表要生成的文件或要执行的任务
main.o),也可以是一个动作名(如 clean)。make 通过 target 来决定"做什么"。
目标所依赖的文件或前置条件
以 Tab 开头的 shell 命令,描述如何从依赖生成目标
gcc -o main main.o、rm -f *.o 等。
存储可复用的值,如编译器名、编译选项
= 或 :=,引用用 $(VAR) 或 ${VAR}。= 是延迟展开(使用时才求值),:= 是立即展开(定义时求值)。还有 ?=(仅在未定义时赋值)和 +=(追加)。
用 % 通配符定义通用构建规则
%.o: %.c 表示"任何 .o 文件都依赖对应的 .c 文件"。$< 代表第一个依赖,$@ 代表目标。这是消除重复代码的利器。
声明不代表实际文件的目标
.PHONY: clean 告诉 make,clean 这个 target 不代表一个叫 "clean" 的文件。即使目录中存在同名文件,make 也会执行它的 recipe。常用的 phony 目标:all、clean、install、test。
make 内置的文本处理和文件操作函数
$(wildcard *.c) 获取匹配文件列表,$(patsubst %.c,%.o,$(SRCS)) 做模式替换,$(shell ls) 执行 shell 命令。函数让 Makefile 更灵活、更 DRY。
将其他 Makefile 文件引入当前文件
include config.mk 会把 config.mk 的内容插入到当前位置。-include(注意前面的减号)则在文件不存在时不报错。适合管理多环境配置、子模块等场景。
先不动手写代码——通过交互演示,直觉理解 make 做了什么。
假设你有一个小程序,由 main.c 和 utils.c 两个源文件组成。编译流程是:
点击 "模拟编译" 看 make 如何处理这些文件
utils.c 后再次 make,它只会重新编译 utils.o 并重新链接——main.o 没有变化,所以不会重新编译。上面演示的编译逻辑,写成 Makefile 是这样的:
每条规则的结构是固定的:
动手补全 Makefile,在实战中掌握变量、伪目标和模式规则。
现在编译器名 gcc 散落在多处。如果以后要换编译器(比如 clang),需要改很多地方。用变量统一替换。
在下方编辑器中,把 3 个 TODO 标记替换为正确的变量定义或引用。完成后点击"检查答案"。
CC = gcc$(CC) 或 ${CC}$(CFLAGS) 和源文件名。
如果项目目录下恰好存在一个名为 clean 的文件,make 会认为 clean 已经"是最新的"而不执行删除操作。用 .PHONY 解决。
.PHONY: target1 target2 ...all 和 clean 两个目标为 PHONY。
main.o 和 utils.o 的编译命令几乎一样,只是文件名不同。用模式规则 %.o: %.c 一条搞定所有 .c → .o 的转换。
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@$< = 第一个依赖文件(如 main.c)$@ = 目标文件(如 main.o)-c $< 编译该源文件,-o $@ 指定输出文件名。
从零写一个完整的 Makefile。没有逐步提示,只有需求描述和验收标准。
你有一个 Web 前端项目,结构如下:
make build:将 src/ 下的所有文件复制到 dist/,并对 CSS 和 JS 进行压缩(用注释模拟即可)make clean:删除 dist/ 目录下所有文件make dev:启动一个本地开发服务器(模拟命令即可,如 echo)验收标准:Makefile 包含 build/clean/dev 三个目标,使用变量,声明了 .PHONY。
SRC_DIR = srcDIST_DIR = dist.PHONY: all build clean devall: buildbuild: mkdir -p $(DIST_DIR) cp -r $(SRC_DIR)/* $(DIST_DIR)/ @echo "构建完成!"@ 前缀让 make 不打印该命令本身,只打印输出。
综合运用所学知识,处理更复杂的构建场景。这些能力会让你在实际项目中大放异彩。
一个有 20 个 .c 文件的项目,你不可能一个一个写。用 wildcard 和 patsubst 函数自动发现源文件并生成对应的 .o 文件列表。
$(wildcard src/*.c) 返回 src/ 下所有 .c 文件的列表$(patsubst src/%.c, build/%.o, $(SRCS)) 把 src/xxx.c 变成 build/xxx.obuild/%.o: src/%.c 可以匹配任意一对 .c 和 .o$^ 代表所有依赖文件(空格分隔)mkdir -p build 确保输出目录存在!
真实项目往往需要在 debug 和 release 模式之间切换。用 make 的条件语句实现。
ifdef VARIABLE ... else ... endififeq ($(VAR), value) ... else ... endifDEBUG ?= 1 设置默认值(仅在未定义时赋值),然后用 ifeq 判断。
Makefile 不只是编译工具!很多团队用它做项目管理。写一个 Makefile 实现以下目标:
make setup:安装依赖(pip install / npm install)make test:运行测试make lint:代码检查make docker:构建 Docker 镜像make deploy:依赖 build + docker + test 三个目标检验你的理解程度。6 道题,测试理解而非记忆。
随时查阅的核心概念。
你已经掌握了 Makefile 的核心能力。下一步,去真实项目中实践吧。
make -n(dry run)调试VPATH 和 vpath 搜索路径