单合一个 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:兼容期——"先加后删"

分两步走,让中间态也能工作:

  1. 第一刀:两边都能兼容(新变量先加上,旧变量保留);
  2. 第二刀:确认新路径稳定后,再删除旧变量和旧检查。
  • 优点:任何中间态都是合法状态,谁先合都不会挂;
  • 缺点:周期拉长,需要有人记得回来"删旧的"。

方案 3:默认值兜底

给变量一个合理的默认值,让"变量为空"不再等于"熔断"。

  • 优点:改动最小;
  • 缺点:可能把"配置错误"变成"静默降级",反而更隐蔽——慎用。

怎么选? 一个简单的判断:

  • 改动小、评审能协调 → 同批合入;
  • 跨团队、合并节奏不可控 → 走兼容期;
  • 涉及默认行为 → 慎用兜底,宁可失败也不要静默。

六、怎么把"顺序依赖"写清楚

本例的作者做了一件很值得学的事:在 PR 描述里显式写明约束。好的描述应该包含四个要素:

  1. 依赖谁:另一个仓的哪个 PR / 哪个提交;
  2. 谁先谁后:明确"哪一侧先合";
  3. 不合会怎样:写清失败现象(本例是"空变量熔断失败"),让后来人一看到报错就知道是自己漏合了;
  4. 验证范围:本例写了"已全量构建验证、链接无回归"——说明作者验证过的不只是编译通过,还包括链接结果。

反过来说,评审人也应该把"有没有跨仓依赖"当成一个固定检查项,而不是等构建挂了再回头找。

七、合入前的预检清单

  • 这次改动,有没有动到"别的仓也在用"的东西?(变量、配置、接口、库名、宏)
  • 如果只合我这一个仓,处于中间态时构建会怎样?(能过 / 必挂 / 侥幸过)
  • 如果会挂,是把约束写进 PR 描述,还是改成兼容期两步走?
  • 配套 PR 是谁的、合入顺序是什么?(写清楚,别口头约定)
  • 我验证到哪一步?(只过了编译,还是过了链接 / 完整构建)
  • 有没有"静默降级"的风险?(构建能过,但行为已经不对)

最后一条最容易被漏掉。 构建失败是好事,静默降级才是坏事。

八、小结

  • OpenHarmony 多仓架构带来并行开发能力,但跨仓改动没有原子性,于是产生"合并顺序依赖"。
  • 跨仓耦合集中在构建期:变量、配置、接口、库名、开关。
  • "空变量熔断"是好的设计——它把隐蔽问题变成显性失败,代价是中间态必然失败。
  • 处理方式三种:同批合入(干净但靠纪律)、兼容期(安全但周期长)、默认值兜底(慎用)。
  • 把依赖关系写进 PR 描述(依赖谁 / 谁先谁后 / 不合会怎样 / 验证范围),评审时固定检查。
  • 真正要防的不是"构建挂",而是**"构建绿了但行为已经不对"**。
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐