跳转至

增量构建

antel build 的目标只有一个:不重编没有变化的东西,也绝不留下一份过期的目标。

数据从哪来

一次编译会同时产生两个文件:

obj/src_main.o     目标文件
obj/src_main.o.d   依赖文件,由 -MMD -MF 生成

依赖文件记的是这次编译真正读过的输入,包括源文件本身和它 include 的所有头文件(系统头文件除外):

helloworld_antel/obj/main.o: main.c include/foo.h

这份依赖清单是增量判断的关键,它解决了一个靠源码列表无法解决的问题:source 里只有 .c 文件,改一个头文件并不会改变任何源文件的 hash,但受影响的编译单元必须重编。

判断哪些要重编

build 分三步:

  1. 算 hash 变化:把配置里的源文件、include_directories 下的文件、以及上次构建记录的依赖文件里出现的全部路径,逐个算 md5,与基线 log/hashes 比较,得到本次变化的文件集合
  2. 挑出受影响的编译单元:遍历配置里的每个源文件,只要满足下面任意一条,就需要重新编译
    • 目标文件不存在(被手工删掉、或构建中断)
    • 依赖文件不存在(例如没有走过一次完整编译)
    • 源文件本身在变化集合里
    • 该编译单元的依赖文件里,有文件在变化集合里
  3. 按需编译后再统一链接:只编译第 2 步挑出来的源文件,然后重新链接,最后用这次构建的全部输入刷新 hash 基线。编译阶段默认并行(jobs,见配置参考),并发度只影响速度,不影响判定结果

变化清单与重编清单都会落盘,便于核对:

log/hashes_diff    发生变化的文件,以及变化前后的 hash
log/stale_files    本次实际重新编译的源文件

一个例子

三个源文件共用一个头文件:

a.c  b.c  main.c        都 #include "inc/common.h"

只改 inc/common.h,然后 antel build:

计算文件 hash...
发生变化的文件:1
全部待编译文件:3
需重新编译文件:2

只有 a.c、b.c 被重编,main.c 不动。再只改 main.c,需重新编译文件:1。

hash 基线

log/hashes 记录的是上次成功构建的全部输入文件与它们的 md5。基线在构建成功之后才刷新,因此一次失败的构建不会被当成「已经编好了」。

基线文件不存在、或读到不认识的内容(例如从旧版本升级上来的旧格式基线),会提示一次并按全量构建处理,跑一次 rebuild 即可回到增量状态。

什么时候不会增量

情况 行为
基线不存在(首次构建、clean 之后) 全部源文件视为变化,做一次全量构建
基线格式无法识别 同上,并打印一次提示
obj/ 或 log/ 被删 目标文件缺失,逐个补编
某个目标文件被手工删除 该目标文件补编,其余不动
执行 antel rebuild 清空 obj/ 与 log/,无条件全部重编

常见问题

build 说「项目没有改动」但我明明改了东西?

先看 log/hashes_diff:如果它是空的,说明变化的文件不在跟踪范围内。只有 source、include_directories 下的文件,以及上次编译记录过的依赖才参与判断。比如一个从未被任何源文件 include 的头文件,改了它不会触发重编——这是对的。若确认改的文件在跟踪范围内,用 antel rebuild 重建基线。

为什么改了头文件却只重编了一部分源文件?

这正是依赖文件的作用:只有 include 了该头文件的编译单元需要重编。可以在 log/<输出目录>/obj/*.d 里看到每个编译单元的真实依赖。

构建失败了,基线会不会被改坏?

不会。编译或链接返回非 0 时立即终止,基线只在构建成功之后刷新,下次 build 仍会重编失败的部分。