单合一个 PR 就挂?OpenHarmony 多仓的合并顺序依赖
单合一个 PR 就挂?OpenHarmony 多仓的合并顺序依赖
一个 PR 单独合进去,构建立刻挂;把配套的另一个仓的 PR 一起合,就没事。这种"必须同批合入"的约束从哪来,怎么提前发现?
一、现象:单合一个 PR,构建就挂
在一份 PR 描述里,作者写了一句话,值得所有做 OpenHarmony 多仓工程的人留意:
合并顺序依赖:需与另一仓库的
<PR 编号>提交(撤销某配置文件、删除某构建变量的导出)同批合入——那一侧先合、本 PR 未合时,构建会因空变量熔断失败。
翻译成大白话:
- 仓库 A 做了一次精简:不再导出某个构建变量;
- 仓库 B 还在检查这个变量,如果它是空的就主动熔断;
- 于是只合 A 不合 B(或只合 B 不合 A),中间态必然构建失败。
这不是代码写错了,而是**"两个仓的改动之间存在时序约束"**。在 OpenHarmony 这种由几十上百个 git 仓组成的工程里,这类约束非常常见,也非常容易被忽略。
二、为什么 OpenHarmony 是多仓
OpenHarmony 采用多仓管理:系统不是一个巨大的单体仓,而是按子系统、部件拆成大量独立 git 仓,再用 repo 统一管理。
这么做的好处很明显:每个仓可以独立演进、独立评审、独立授权,几十个团队并行开发不会互相踩脚。
但代价也很直接:一个功能的完整改动,可能横跨多个仓。 而各仓的 PR 是各自独立合并的——没有一个全局事务能保证"这些 PR要么全合、要么全不合"。
这就是"合并顺序依赖"的根源:多仓架构给了并行能力,但没给跨仓的原子性。
三、跨仓依赖长什么样
跨仓的耦合,通常落在下面几种东西上:
| 耦合形式 | 例子 |
|---|---|
| 构建变量 | A 仓导出 XXX_LIBS,B 仓用它拼接链接参数 |
| 配置文件 | A 仓维护的 *_lib_config.json,B 仓读取后决定链接哪些库 |
| 接口/结构体 | A 仓改了函数签名,B 仓的调用点没跟着改 |
| 库产物名 | A 仓把 libxxx.a 换了名字或归属,B 仓的链接脚本还在找老名字 |
| 开关宏 | A 仓删掉了某个开关,B 仓的"开关未定义"分支行为不明 |
注意一个共同点:它们都发生在"构建期"而不是"运行期"。 也就是说,耦合错了,编译/链接阶段就会暴露——这反而是好事,因为能在合入前发现。
但前提是:你有机会测到那个中间态。 而多仓各自独立合并的流程,恰恰经常让人测不到。
四、中间态为什么必然失败:拆解一次真实的"空变量熔断"
回到开头那个例子,完整的因果链是这样的(下图是脱敏后的结构):
【仓库 A(构建配置)】
变更:不再导出变量 X
└─> X 变为「未定义 / 空」
【仓库 B(组件适配)】
原有逻辑:当 X 为空时,主动报错熔断(防止默默链接到错误的库)
【合入检查】
├─ 只合 A:B 仍检查 X 为空 → 熔断 → 构建失败
├─ 只合 B:B 不再检查 X,但 A 还没合并
│ → X 仍由 A 正常导出 → 可能侥幸通过,但下次 A 合并就会出问题
└─ A、B 同批合:X 的"导出方"和"检查方"同步切换 → 一致 → 通过
这里最值得注意的是 B 仓那个 "熔断"设计:
它本意是好的——当依赖的变量缺失时,宁可构建失败,也不要静默地链接出错的库。这类"快速失败(fail fast)"的设计,比"默默编过、运行期炸掉"要好得多。
但它也带来了副作用:它把一个原本"可能侥幸通过"的中间态,变成了"必然失败"的中间态。
这其实是一件好事:失败被提前了,而且原因清晰("空变量熔断"直接指向了缺依赖)。真正危险的是相反的情况——A 仓的改动在 B 仓被静默忽略,构建照样绿,直到某天运行期发现行为不对。
五、三种拆法
遇到这种跨仓时序约束,有几种常见处理方式,各有取舍:
方案 1:同批合入(本例采用)
把两个仓的 PR 绑定,要求一起合并。
- 优点:逻辑最干净,没有过渡态;
- 缺点:依赖人工纪律和评审配合,一旦有人提前合了一个,构建就挂。
方案 2:兼容期——"先加后删"
分两步走,让中间态也能工作:
- 第一刀:两边都能兼容(新变量先加上,旧变量保留);
- 第二刀:确认新路径稳定后,再删除旧变量和旧检查。
- 优点:任何中间态都是合法状态,谁先合都不会挂;
- 缺点:周期拉长,需要有人记得回来"删旧的"。
方案 3:默认值兜底
给变量一个合理的默认值,让"变量为空"不再等于"熔断"。
- 优点:改动最小;
- 缺点:可能把"配置错误"变成"静默降级",反而更隐蔽——慎用。
怎么选? 一个简单的判断:
- 改动小、评审能协调 → 同批合入;
- 跨团队、合并节奏不可控 → 走兼容期;
- 涉及默认行为 → 慎用兜底,宁可失败也不要静默。
六、怎么把"顺序依赖"写清楚
本例的作者做了一件很值得学的事:在 PR 描述里显式写明约束。好的描述应该包含四个要素:
- 依赖谁:另一个仓的哪个 PR / 哪个提交;
- 谁先谁后:明确"哪一侧先合";
- 不合会怎样:写清失败现象(本例是"空变量熔断失败"),让后来人一看到报错就知道是自己漏合了;
- 验证范围:本例写了"已全量构建验证、链接无回归"——说明作者验证过的不只是编译通过,还包括链接结果。
反过来说,评审人也应该把"有没有跨仓依赖"当成一个固定检查项,而不是等构建挂了再回头找。
七、合入前的预检清单
- 这次改动,有没有动到"别的仓也在用"的东西?(变量、配置、接口、库名、宏)
- 如果只合我这一个仓,处于中间态时构建会怎样?(能过 / 必挂 / 侥幸过)
- 如果会挂,是把约束写进 PR 描述,还是改成兼容期两步走?
- 配套 PR 是谁的、合入顺序是什么?(写清楚,别口头约定)
- 我验证到哪一步?(只过了编译,还是过了链接 / 完整构建)
- 有没有"静默降级"的风险?(构建能过,但行为已经不对)
最后一条最容易被漏掉。 构建失败是好事,静默降级才是坏事。
八、小结
- OpenHarmony 多仓架构带来并行开发能力,但跨仓改动没有原子性,于是产生"合并顺序依赖"。
- 跨仓耦合集中在构建期:变量、配置、接口、库名、开关。
- "空变量熔断"是好的设计——它把隐蔽问题变成显性失败,代价是中间态必然失败。
- 处理方式三种:同批合入(干净但靠纪律)、兼容期(安全但周期长)、默认值兜底(慎用)。
- 把依赖关系写进 PR 描述(依赖谁 / 谁先谁后 / 不合会怎样 / 验证范围),评审时固定检查。
- 真正要防的不是"构建挂",而是**"构建绿了但行为已经不对"**。
更多推荐
所有评论(0)