多目标资产构建:单目标重建不能覆盖其他制品
自动关联目录:多目标资产构建:单目标重建不能覆盖其他制品
核心判断:多目标构建的提交单元不是“本次命令产出的目录”,而是“一个逻辑资产修订下的完整变体集合”;单目标重建只能替换自己的槽位,不能把其他已验证变体从索引中抹掉。
一、业务冲突:局部重建不能破坏全局可用性
一个源资产需要被多个运行目标消费。完整构建已经产出桌面、移动端和轻量目标的制品后,开发者为了快速预览,只重建其中一个目标。
最直接的实现是用“本次构建结果”重写顶层索引:
index.variants = artifactsBuiltThisRun命令本身会成功,刚构建的目标也能加载,但其他目标会从索引中消失。它们的制品字节可能仍在磁盘上,系统却不再承认其存在。
这不是普通的增量构建问题,而是发布语义错误:一次局部操作覆盖了比自己更大的事实范围。
二、实际实现:目标槽位独立发布,顶层索引合并
现有实践已经形成了较完整的防线:
flowchart LR
A[冻结源身份] --> B[目标私有 staging]
B --> C[生成 Manifest 与包]
C --> D[独立校验 staging]
D --> E{目标槽位已存在?}
E -->|同身份且校验通过| F[视为缓存命中]
E -->|不存在| G[原子移动到目标槽位]
E -->|身份冲突| H[失败,不覆盖]
F --> I[读取其他已验证目标槽位]
G --> I
I --> J[合并并稳定排序变体]
J --> K[临时索引原子替换正式索引]每个平台制品都携带足够的身份和验证信息:逻辑资产、内容修订、源摘要、目标配置、工具链指纹、包摘要和大小等。构建先在作业私有目录完成,再由独立读取路径校验;只有验证通过,才进入规范目标槽位。
顶层索引不是只写本次输出。它还会扫描未参与本次构建的规范目标槽位,并且只保留满足以下条件的旧变体:
- Manifest 和包字节可以独立验证;
- 逻辑资产与内容修订一致;
- 源摘要和构建配置一致;
- 根对象及文件计数等关键身份一致。
因此,单目标重建实际执行的是“替换一个槽位并合并其余有效槽位”。
三、值得保留:正确性已经进入提交协议
这个实现有四点值得保留。
第一,staging 隔离。 构建失败不会在正式目录留下半个包,处理阶段也不能直接污染可用结果。
第二,Writer 与 Verifier 分离。 产物不是因为“刚写完”就可信,而是重新从 Manifest 和字节验证。它能发现写入逻辑自洽但 Reader 实际不可消费的问题。
第三,已存在目录不静默覆盖。 同身份且同内容才复用;身份冲突直接失败。这避免两个构建把同一规范位置解释成不同结果。
第四,索引按集合语义更新。 旧变体只有经过重新验证并证明属于同一源身份才会保留。增量构建因此不是“少做几步”,而是“复用可证明等价的结果”。
四、结构性问题:平台目录和顶层索引仍是两次提交
当前方案解决了“单目标重建抹掉其他目标”,但还没有形成真正的跨文件事务。
1. 目标槽位与索引存在崩溃窗口
新平台目录先原子移动成功,顶层索引随后才原子替换。若进程在两步之间终止,会出现两种状态:
- 新制品有效但索引未引用,形成孤儿变体;
- 重试时发现目标槽位已存在,需要额外判断它是可复用结果还是冲突残留。
单次 rename 只能保证一个路径切换,不能自动把两个独立路径变成事务。
2. 合并谓词是隐含兼容协议
“哪些字段相等才可保留旧变体”散落在校验逻辑中。未来新增影响字节语义的字段,如果忘记加入身份比较,旧制品可能被错误复用;如果一律失效,又会造成不必要的全量重建。
3. 并发 Writer 仍需显式仲裁
两个进程同时读取旧索引、分别构建不同目标,再各自写回时,后提交者可能覆盖先提交者刚加入的变体。作业内隔离不能解决跨作业的 lost update。
4. 失败只有“拒绝”,缺少修复闭环
发现规范槽位存在但身份冲突时,立即失败是正确的默认行为;但系统还需要明确的隔离、诊断和垃圾回收工具,否则人工删除目录会重新引入不可审计状态。
五、推荐方案:不可变 generation + 单指针提交
更完整的方案是把一个逻辑修订的全部平台变体写入新的不可变 generation,最后只切换一个当前指针:
PublishVariant(asset, revision, target, candidate):
verified = independentlyVerify(candidate)
base = loadCurrentGeneration(asset, revision)
next = cloneVerifiedVariants(base)
next.variants[target] = verified
next.identity = canonicalBuildIdentity(revision, toolchain, profile)
generationDir = writeImmutableGeneration(next)
verifyGenerationIndex(generationDir)
compareAndSwapCurrent(
expectedGeneration = base.id,
newGeneration = generationDir.id)关键变化有两个:
- 平台目录和变体索引位于同一个不可变 generation 中,构建完成前对 Reader 不可见;
current指针通过比较并交换更新,两个并发 Writer 不会静默覆盖彼此,失败者必须基于新 generation 重新合并。
内容寻址的包可以跨 generation 复用,generation 只保存小型索引和引用。这样既保留增量构建,也把最终提交收敛为一次原子切换。
六、取舍与验证结论
目标槽位合并方案实现成本较低,也已经能正确支撑串行的单目标重建;不可变 generation 则增加了索引、引用计数、并发控制和垃圾回收成本。是否升级,取决于是否已经出现并发构建、频繁崩溃恢复或严格版本回退需求。
现有自动化验证已经覆盖了核心业务合同,但仍有明确缺口:
| 场景 | 已证明的结果 | 仍需补充 |
|---|---|---|
| 一次生成多个目标 | 各变体共享逻辑身份,包可独立校验 | 处理器升级后的精确失效范围 |
| 随后只重建一个目标 | 其他有效变体仍保留在顶层索引 | 新旧变体并发写入的冲突处理 |
| staging 校验失败 | 不发布候选制品 | 每个文件系统上的崩溃恢复测试 |
| 已存在同身份制品 | 可验证后复用 | 冲突制品的隔离与修复工具 |
| 临时索引写入 | 正式索引不暴露半写内容 | 平台目录已提交、索引未提交的断电窗口 |
我的反思是:过去容易把“命令成功”“包已生成”“单平台能加载”当作构建完成。更准确的验收对象应该是提交后的资产版本集合:所有被索引的变体是否身份一致、可独立验证,局部重建是否只改变授权范围,任意中断点是否仍保留上一代完整状态。
决策规则很直接:
- 单进程、低并发工具链,可以采用“平台槽位原子发布 + 校验后合并索引”;
- 一旦需要并发构建、可靠回退或严格崩溃一致性,就应升级为“不可变 generation + 单指针提交”;
- 无论采用哪种方案,都不能用本次命令的输出集合直接覆盖资产的完整变体集合。
资产身份的前置判断见:资产身份:路径能找到文件,但不能证明文件正确。运行时获取的所有权问题见:共享下载:工作可以合并,所有权不能合并。