模块:M01 心智模型与 make 入口
篇号:M01-04 / 41
预计阅读:35 分钟
主线:GNU Make
本篇目标
掌握日常最高频的一组make命令行开关:并行(-j)、失败后是否继续(-k)、只演练不执行(-n)、强制重编(-B)、安静模式(-s),并理解选项如何通过MAKEFLAGS传递给子 make。学完本篇,M01 模块收官:你能在「读入口、找文件、定默认目标」之外,主动选用正确开关完成构建与排错。
30 秒速览
-j [N]:并行执行互不依赖的配方;不写N时尽量用多任务(慎用,见 M08-01)。-k:某目标失败后不立刻停,尽量继续做其它目标(keep going)。-n:打印将执行的命令,不真正执行(dry-run);排错首选。-B/--always-make:视所有目标为过期,强制重跑配方。-s/--silent:不回显配方命令行(类似配方前加@的全局版)。MAKEFLAGS:make 把自己的标志放进环境,子$(MAKE)常会继承;乱设环境变量可能「悄悄改变」构建行为。
正文
1. 为什么先学开关,而不是埋头写更复杂规则?
M01-01~M01-03 解决「规则是什么、读哪份文件、默认做谁」。
真正干活时,你还经常需要:
| 诉求 | 常用开关 |
|---|---|
| 想快一点 | -j |
| 想先看清会跑什么 | -n |
| 改完头文件仍觉得没重编 | -B(或先查依赖是否写对,见 M06) |
| 一处失败想看后面还有没有错 | -k |
| 日志太吵 | -s |
这些开关不改变 Makefile 语法,但强烈改变「怎么跑」。读 CI 脚本、同事命令时,也要能一眼认出它们。
2.-n:只演练,不执行(排错第一工具)
make-nmake-ncleanmake-nhello效果:打印本将执行的配方命令,默认不真正跑gcc/rm等。
典型用途:
- 换目录 / 换
-f后,确认路径是否指对(呼应 M01-02)。 - 改完规则,先看会不会误删、误链。
- 对照「你以为会编的文件」和「make 实际要编的文件」。
相关:
| 选项 | 含义 |
|---|---|
-n/--just-print/--dry-run | 打印不执行 |
-p/--print-data-base | 打印 make 读入后的数据库(变量、规则),输出很大,M09-01 再用 |
本篇记住:先-n,再真跑。
3.-B:强制全部当作过期
make-Bmake--always-make含义:不信任「已是最新」的判断,强制重做需要更新的目标链(行为上像一切都过期)。
适用:
- 怀疑时间戳异常(拷贝文件保留了旧 mtime、网络文件系统时钟偏斜)。
- 临时绕过「明明改了却不重编」——但根因往往在依赖没写全(M06),
-B是止痛不是治本。
对照:
| 做法 | 效果 |
|---|---|
make -B | 强制按规则重跑 |
make clean && make | 先删产物再编;依赖clean是否删干净 |
只touch某个源文件 | 只让依赖该源的目标过期 |
4.-j:并行构建
make-jmake-j8make-j$(nproc)# Linux 上常用:按 CPU 数含义:在依赖允许的前提下,同时跑多个配方。
要点(本篇先建立正确预期,细节与竞态见M08-01):
- 没有依赖关系的目标(如多个独立的
.o)可以并行。 - 配方若写得不安全(两个任务写同一临时文件、未声明的次序依赖),
-j会把隐性 bug放大成偶发失败。 -j不带数字时,GNU Make 会启动较多并行任务,机器可能很卡——团队脚本里更常见写死-j$(nproc)或-j8。
读命令时:看到 CI 里make -j$(nproc),说明他们在用并行加速;本地复现失败时,可先试make -j1(串行)判断是否并行竞态。
5.-k:失败后尽量继续
默认行为:某个目标的配方失败(非零退出),make立刻停止(已启动的并行任务行为另说)。
make-kmake--keep-going含义:一个目标失败后,继续尝试其它不依赖该失败结果的目标。适合:
- 一次暴露多处编译错误(修 bug 阶段)。
- 大型树里先扫一遍「还有哪些模块挂了」。
不适合:
- 发布流水线「失败即停」的场景——那里通常不要
-k,以免带着半残产物继续走。
6.-s与配方回显
make-smake--silentmake--quiet含义:不打印配方命令行本身(仍会看到命令自己的 stdout/stderr)。
对照配方里的@:
hello: hello.c @echo "building hello" gcc -o hello hello.c| 写法 | 效果 |
|---|---|
配方前@ | 该行不回显 |
make -s | 全局更安静 |
| 默认 | 先打印命令,再执行 |
排错时常常去掉-s,好看到真实命令;需要干净日志再加回。
7. 其它高频开关(扫一眼即可)
| 选项 | 含义 |
|---|---|
-e | 环境变量覆盖 Makefile 中的同名变量(危险,易让构建「环境相关」;慎用) |
-i | 忽略配方错误(类似每行前加-;更危险,少用) |
-r | 不使用内置规则 |
-R | 不使用内置变量与规则(更干净的「空白」环境,高级场景) |
-w/--print-directory | 进入/离开目录时打印提示(递归 make 时很有用) |
-C DIR | 换目录(M01-02) |
-f FILE | 指定文件(M01-02) |
本篇核心记住:-n-j-k-B-s;其余遇到再查。
8.MAKEFLAGS:选项如何「跟着环境走」
GNU Make 会把当前的命令行标志放进环境变量MAKEFLAGS(以及相关机制),使得子进程里的 make(尤其配方中的$(MAKE))常常继承同样的并行度等设置。
示意:
$exportMAKEFLAGS="-j4"$make# 本次以及子 make 可能都带上 -j4 一类标志你也会在脚本里看到:
make-j8# 内部若有 $(MAKE) -C subdir,子 make 往往仍带并行相关 flags读工程时要注意:
- 环境里残留的
MAKEFLAGS可能导致「我明明没写-j,却并行了」。排查:echo "$MAKEFLAGS"。 - 配方里应写
$(MAKE)而不是裸make,以便正确传递 flags / 作业服务器信息(M08-02 展开)。写裸make是常见坏味道。 - 临时取消:
env -u MAKEFLAGS make(或在当前 shellunset MAKEFLAGS)再试。
还有MFLAGS等历史相关变量;日常优先会查MAKEFLAGS即可。
9. 组合示例:怎么选?
| 场景 | 建议命令 |
|---|---|
| 第一次碰陌生项目 | make -n看将执行什么 |
| 日常开发构建 | make -j$(nproc)(规则可靠时) |
| 怀疑并行问题 | make -j1 |
| 想强制重来 | make -B或make clean && make |
| 一次看清多处编译错误 | make -k(或make -k -j1) |
| CI 要安静日志 | make -s(仍建议保留失败时的编译器输出) |
| 子目录构建 | make -C path -j8 |
组合没有唯一标准,但有一条原则:
先正确,再并行;先
-n看清,再去掉-n真跑。
10. 对照表:本篇开关
| 选项 | 一句话 |
|---|---|
-n | 打印命令,不执行 |
-B | 强制当过期,重做 |
-j[N] | 并行 |
-k | 失败后继续做其它目标 |
-s | 少回显命令行 |
-C/-f | 换目录 / 指定文件(M01-02) |
MAKEFLAGS | 标志的环境载体;影响子 make |
读 Makefile / 读构建命令检查清单
看到同事或 CI 的构建行时:
- 有没有
-n?若有,那只是演练 - 有没有
-j?失败可否改用-j1复现 - 有没有
-k?失败是否被「带过」了 - 有没有
-B?是在掩盖依赖问题吗 - 环境变量
MAKEFLAGS是否被导出?echo "$MAKEFLAGS" - 子目录调用写的是
$(MAKE)还是裸make?
练习
判断题(对 / 错)
make -n会真正执行gcc并生成目标文件。make -j1表示串行执行配方(并行度为 1)。- 环境变量里残留的
MAKEFLAGS=-j8可能影响你「以为自己在串行」的一次构建。 make -k适合作为发布流水线的默认选项,以便尽量多产出文件。
- 错。
-n默认只打印不执行。 - 对。
- 对。应用
echo "$MAKEFLAGS"排查,必要时unset MAKEFLAGS。 - 错。发布/门禁通常希望失败即停;
-k更适合本地扫错。
改错题
CI 脚本写了:
exportMAKEFLAGS="-i -k"make-j$(nproc)all从「可靠发布」角度看有什么问题?如何改更稳妥?
参考答案问题:
-i忽略错误,失败也可能继续,产物不可信。-k同样让失败后继续,门禁语义变弱。- 把危险标志放进
MAKEFLAGS还会传给子 make,影响面更大。
更稳妥示例:
unsetMAKEFLAGSmake-j"$(nproc)"all# 需要扫错时再临时:make -k -j1动手题
- 对你的最小
hello工程执行make -n,对照不带-n的差异。 export MAKEFLAGS=-j2后执行make,再unset MAKEFLAGS对比。- 故意在配方里写一条会失败的命令,对比默认停止与加
-k的行为。
M01 小结(本模块收官)
| 篇 | 你应带走的能力 |
|---|---|
| M01-01 | 认出目标 / 依赖 / 配方,理解时间戳增量 |
| M01-02 | 找对入口文件与工作目录(-f/-C) |
| M01-03 | 默认目标、.PHONY、all/clean约定 |
| M01-04 | 用-n/-j/-k/-B/-s与MAKEFLAGS驾驭构建过程 |
下一模块M02进入规则语法精读:多目标、模式规则%、遗留 suffix 规则等——那是读大型 Makefile 的主战场。
下一篇预告
M02-01:《显式规则:目标、依赖、配方与 Tab 纪律》— 把规则语法钉死:多行配方、行续接、@/-/+前缀,以及 Tab 与空格的纪律。学完 M02-01,你写每一条规则时都有章可循。