3. 基于 Makefile 的多步骤计算依赖编排

为什么用 Makefile 表达计算依赖

科研计算的多步骤任务本质上是一张文件依赖图:原始数据经预处理生成中间文件,中间文件驱动计算产生结果,结果再被汇总成报告或图。Makefile 的核心语义恰好是“目标文件比它的依赖旧,才执行命令”,因此只要把每个步骤写成“目标: 依赖 + 命令”,就能天然获得增量执行能力——修改某个输入后,只有依赖链下游的目标会被重算。

本例构造一个最小但完整的三步计算链:raw.csv →(预处理)→ clean.csv →(计算)→ result.txt →(汇总)→ summary.md。所有命令用标准 Unix 工具实现,无需额外依赖,可直接复现。

文件依赖图(箭头指向目标,标注生成命令) raw.csv 原始数据(输入) clean.csv 预处理输出 result.txt 计算结果 summary.md 汇总报告(最终) preprocess compute report 场景 A:修改 raw.csv 后执行 make raw.csv 已改 重算 clean.csv 重算 result.txt 重算 summary.md 场景 B:仅修改 clean.csv 后执行 make raw.csv 未变 clean.csv 已改 重算 result.txt 重算 summary.md 虚线 = 跳过(依赖未更新);橙色 = 实际执行;灰色 = 未触发

编写 Makefile

在项目目录下创建 Makefile。注意 Makefile 的缩进必须是Tab 字符,不能用空格。下面每个目标都声明了它依赖的文件,命令只做最小必要处理:

# 变量:便于统一修改输入与输出名
RAW    := raw.csv
CLEAN  := clean.csv
RESULT := result.txt
SUMMARY:= summary.md

# 默认目标:make 不带参数时执行第一个目标
all: $(SUMMARY)

# 预处理:去掉空行与注释行,按第一列排序
$(CLEAN): $(RAW)
	@echo "[preprocess] $< -> $@"
	grep -v -e '^#' -e '^$$' $< | sort -t, -k1,1n > $@

# 计算:对第二列求和,写入结果文件
$(RESULT): $(CLEAN)
	@echo "[compute] $< -> $@"
	awk -F, '{s+=$2} END{printf "sum=%.3f\n", s}' $< > $@

# 汇总:把结果包装成 Markdown 报告
$(SUMMARY): $(RESULT)
	@echo "[report] $< -> $@"
	{ echo "# 计算结果汇总"; echo; echo "数据来源: $(CLEAN)"; echo; cat $<; } > $@

# 清理生成物,便于从头复现
clean:
	rm -f $(CLEAN) $(RESULT) $(SUMMARY)

.PHONY: all clean

关键点:$< 是第一个依赖(输入文件),$@ 是目标文件。命令里用 > 重定向到 $@,保证目标文件确实被更新,Make 才能正确比较时间戳。

准备输入并首次执行

创建示例输入 raw.csv(第一列为编号,第二列为测量值):

# id,value
3,1.5
1,2.0
2,3.25

执行 make,预期输出(示例):

[preprocess] raw.csv -> clean.csv
[compute] clean.csv -> result.txt
[report] result.txt -> summary.md

此时 summary.md 内容为:

# 计算结果汇总

数据来源: clean.csv

sum=6.750

验证增量执行

验证 1:无改动时不应重算。 再次执行 make,预期输出:

make: 'summary.md' is up to date.

说明所有目标都比依赖新,Make 跳过了全部命令。

验证 2:只改输入,触发全链重算。 修改 raw.csv 中某行的值(例如把 2,3.25 改为 2,4.00),再执行 make,预期三步全部重新执行,summary.md 中的 sum 变为 7.500。

验证 3:只改中间文件,只重算下游。 先执行 make clean 再 make 生成全部文件;然后手动修改 clean.csv(例如追加一行 4,0.5),执行 make。预期输出只有:

[compute] clean.csv -> result.txt
[report] result.txt -> summary.md

预处理步骤被跳过,因为 raw.csv 未变。这正是“仅重算受影响步骤”的核心行为。

若想观察 Make 的决策过程,可用 make -n 只打印将执行的命令而不真正执行;用 make -d 可查看时间戳比较的详细日志,适合排查“为什么没重算”的问题。
常见陷阱:命令中若没有真正写入目标文件(例如只 echo 而不重定向),目标文件时间戳不会更新,Make 会认为目标始终过期,导致每次 make 都重复执行。务必确保每条命令都更新 $@。

可直接使用的检查清单

检查项通过标准
缩进命令行为 Tab 开头,非空格
依赖声明每个目标都列出其直接输入文件
目标更新命令确实写入 $@
无改动重跑make 输出 up to date
改输入重跑仅下游目标被重算
可清理make clean 后能从头复现