调试第一步:使用 make -n(干运行)查看执行计划

说实话,我见过太多人一遇到 Makefile 报错就慌了。

直接 make 一把梭,然后盯着满屏的错误日志发呆。

其实没必要。你想想看,Makefile 本质上就是个「任务调度器」。它怎么执行、先执行谁、依赖什么文件——这些信息,完全可以在真正运行之前就摸清楚。

怎么摸?用 make -n。

什么是干运行?

make -n 也叫 dry run。说白了就是「只打印,不执行」。

它会把你 Makefile 里所有要执行的命令,一条一条列出来。但不会真的去编译、链接、拷贝文件。就像看剧本,不演出来。

我个人习惯,每次修改 Makefile 之后,第一件事就是跑一遍 make -n。确认执行计划没问题了,再真正去构建。

核心价值: 在零风险的前提下,看清 Makefile 的完整执行路径。

一个简单的例子

假设你有这样一个 Makefile:

# Makefile
all: hello

hello: main.o utils.o
    gcc -o hello main.o utils.o

main.o: main.c
    gcc -c main.c

utils.o: utils.c
    gcc -c utils.c

clean:
    rm -f *.o hello

执行 make -n,你会看到:

$ make -n
gcc -c main.c
gcc -c utils.c
gcc -o hello main.o utils.o

看到了吗?它把三条命令都列出来了。但此时你的目录里,没有任何 .o 文件和 hello 可执行文件。

这就是干运行——只看,不动手。

什么时候用 make -n?

我总结了几种典型场景:

  • 刚写完 Makefile:检查依赖关系对不对,目标顺序对不对
  • 修改了变量或规则:比如改了 CFLAGS,看看实际编译命令是否生效
  • 怀疑有循环依赖:干运行能帮你发现死循环的苗头
  • 想了解别人的构建流程:拿到一个陌生项目,先 make -n 看看它要干什么
小技巧: 配合 make -p 使用效果更佳。-p 打印所有变量和规则,-n 打印执行计划。两者结合,基本能把 Makefile 的底裤都看穿。

避坑指南:干运行 ≠ 完全安全

嗯,这里要注意。虽然 make -n 不会执行编译命令,但它会执行 Makefile 中的 递归调用。

什么意思?如果你的 Makefile 里有 $(MAKE) -C subdir 这种写法,make -n 会进入子目录继续干运行。这本身没问题。但如果你在子 Makefile 里写了 $(shell ...) 函数——那它真的会执行 shell 命令。

我曾经在一个项目里吃过这个亏。有个同事在 Makefile 里用 $(shell rm -rf /tmp/build) 清理临时文件。我跑 make -n 想看看流程,结果那个 rm 真的被执行了。还好删的是 /tmp 下的目录,要是路径写错了……你懂的。

警告: $(shell ...) 和 $(eval ...) 在 Makefile 解析阶段就会执行,不受 -n 控制。干运行前,最好检查一下有没有这类「副作用」代码。

进阶用法:只查看特定目标的执行计划

有时候你不想看全部,只想看某个目标。比如只想看 clean 会做什么:

$ make -n clean
rm -f *.o hello

这招在调试复杂 Makefile 时特别有用。我经常用它来验证某个规则是否被正确触发。

结合 --debug 选项

光看命令还不够?那就加上 --debug:

$ make -n --debug=v
Reading makefiles...
Updating goal targets....
  File 'all' does not exist.
    File 'hello' does not exist.
      File 'main.o' does not exist.
        Must remake target 'main.o'.
        gcc -c main.c
      File 'utils.o' does not exist.
        Must remake target 'utils.o'.
        gcc -c utils.c
      Must remake target 'hello'.
      gcc -o hello main.o utils.o

你看,它把决策过程都打印出来了。哪个文件不存在、为什么需要重新编译、依赖链是怎样的——一目了然。

我的习惯: 调试阶段,我通常用 make -n --debug=v 2>&1 | tee build.log。既看执行计划,又看决策逻辑,还能保存日志。一举三得。

知识体系:干运行的核心逻辑

为了让你更直观地理解,我画了一张流程图:

make -n 干运行核心逻辑 执行 make -n 阶段1:解析 Makefile 读取规则、变量、条件判断 阶段2:依赖关系决策 检查文件时间戳、决定哪些需要重建 阶段3:打印执行计划 输出命令列表,但不实际执行 输出完成,无副作用 ⚠️ 注意 $(shell ...) 会执行 $(eval ...) 会执行 递归 make 安全 变量展开安全 规则匹配安全 建议:先 grep 检查 $(shell) 的使用

总结一下

make -n 是我用得最频繁的调试命令,没有之一。

它简单、安全、信息量大。你不需要记住复杂的调试技巧,只需要记住一条:在真正执行之前,先看看它打算干什么。

这就像开车前先看一眼导航。虽然你认识路,但看一眼总没坏处,对吧?

一句话记住: 先 make -n,再 make。养成习惯,能省掉你 80% 的调试时间。

可可精品资料库