Build:确定性构建
确定性是配置构建系统的硬要求:相同输入必须得到相同产物。
确定性构建
相同:
Source
Schema
Cage Version
Profile应该得到相同:
Artifact
Hash
Manifest即:
Build(A) == Build(A)避免:
- 时间戳导致文件变化
- 不确定 Map 顺序
- 随机 ID
- 非稳定排序
落地口径:IndexMap 保序 + 显式稳定排序(对象键经 BTreeMap 规范化),产物字节级可复现(golden 测试:同输入两次构建字节级一致),禁止时间戳与随机 ID 进入产物。
Build Manifest
每次构建生成 Manifest(cage-core::manifest,BuildManifest / ArtifactInfo):
{
"project": "game",
"profile": "client",
"environment": "prod",
"cage_version": "0.1.0",
"generator_version": "1.2.0",
"build_id": "...",
"schema_hash": "...",
"source_hash": "...",
"content_hash": "...",
"dependencies": { "Monster": ["DropTable"], "DropTable": ["Item"] },
"table_hashes": { "Item": "...", "DropTable": "...", "Monster": "..." },
"targets": [
{ "format": "json", "output_dir": "build/client", "hash": "..." }
],
"artifacts": {
"item.json": { "path": "item.json", "hash": "...", "size": 123, "format": "json", "table": "Item", "encoding": "utf-8" }
}
}当前字段与账本目标对照(「编译器核心:八个概念的边界」中 Manifest 的差距落地面):
| 字段 | 现状 | 说明 |
|---|---|---|
project / profile | 已实装 | 构建身份 |
environment | 已实装 | 构建所处环境(--env,Schema env_overrides,设计 §48);基线构建(无 --env)不写该键。环境改变 → 解析后 Schema 变 → schema_hash/build_id 轮换,增量守卫显式比对环境,换环境不复用产物 |
cage_version | 已实装 | cage 编译器版本 |
generator_version | 已实装 | Manifest 结构版本("1.2.0";布局演进时自增,独立于 cage 版本) |
build_id | 已实装 | 确定性指纹:blake3(profile + schema_hash + source_hash + content_hash) 前 24 位。同输入同 ID(可复现/匹配/回滚),语义变更即旋转。不用时间戳——那会破坏「相同输入 → 相同 Manifest 字节」的确定性契约 |
schema_hash | 已实装 | Blake3,覆盖 schema 全量 |
source_hash | 已实装 | Blake3,覆盖全部源内容 |
content_hash | 已实装 | Blake3,覆盖全部产物字节 |
artifacts | 已实装 | 每产物 path / hash / size / format / table / encoding |
dependencies | 已实装 | 表间引用账:表 -> 引用表名有序列表(L5 依赖图接线后由 ManifestGenerator 落账,D2;旧 manifest 缺字段经 serde(default) 兼容) |
table_hashes | 已实装 | 每表行级指纹(增量第二层变更检测) |
targets | 已实装 | 每 target 配置指纹:按 (format, output_dir) 排序的 TargetRecord 列表,hash 为 blake3(覆盖 format/output_dir/file_template/options,选项键排序);identity 是 (format, output_dir)(增量第三层变更检测;旧 manifest 缺字段经 serde(default) 兼容) |
ir_hash | 随 IR 定界省略 | IR 与 Canonical 同构(见架构文档),以 schema_hash + source_hash 覆盖 |
用途:
- 版本追踪
- 部署
- 回滚
- 客户端/服务端版本匹配
- CI
- 缓存
- 增量构建
配置依赖图
表间引用构成配置依赖图(cage-core::reference 已实装 DependencyGraph,含 环检测与拓扑排序)。例如:
Monster
|
| DropTableID
v
DropTable
|
| ItemID
v
Item最终:
Monster
|
+----> DropTable
|
+----> Item这个依赖图可以用于:
- 引用检查(L5 校验消费引用拓扑)
- 构建顺序
- 增量构建(第二层:表级依赖图传播;第三层:target 配置传播)
- 变更影响分析
- 删除检查
- 循环依赖检测
- 调试
现状:核心类型与算法已实装并有测试(reference/mod.rs:DependencyGraph / IncrementalPlanner、环检测、拓扑、增量规划),构建路径已接线——引用图由 schema 引用声明经 DependencyGraph::from_schema 构建(先于且独立于 L5 校验运行),L5 校验与 cli 增量第二层共同消费它,manifest 落 dependencies 引用账。核心与 cli 的接线覆盖在 tests/incremental.rs(含第二/三层端到端:变更传播 + 携带 + target 配置 增删改 + manifest 收敛 + 旧版 manifest 迁移)。cage graph(2026-10-12 实装)把同一张图渲染给人看:边带引用字段标签,文本 / Graphviz dot / json 三形态,附拓扑构建序与环清单(见 CLI)。
增量构建
cage build --incremental 分三层。
第一层:哈希比对跳过。 构建时把当前 schema/source/target 指纹与 上一次 manifest.json 记录的值比对,同 profile、同环境、同指纹且产物 都在磁盘上时直接跳过重新生成(校验仍然全量执行);任一输入变化、环境 切换或产物缺失则进入第二/三层或全量重建。旧版 manifest(无 targets 记录)一次全量构建即迁移,随后参与三层。
第二层:依赖图传播(v0.3 实装)。 当 schema 哈希未变(schema 驱动 code-target 形态,schema 变则整体回退全量)、prev 与当前 profile 和 环境相同、 table_hashes 非空且没有 删除表时,按表哈希找出变更表,经依赖图 (IncrementalPlanner::compute_affected)传播出受影响表集合,只重建这 些表:
- 生成输入裁剪到受影响表(code-target 保留完整枚举集:共享 enums 单元 全量重生成,从不携带);
- 未受影响表、且在磁盘上完好的产物从磁盘携带(generator 确定性 ⇒ 携带 字节与全量构建完全一致),产物顺序按路径排序规范化,合并后的 manifest 与全量构建逐字节一致(确定性契约)。
第三层:target 传播(v0.4 实装)。 manifest 记录每个 target 的配置 指纹(targets:按 (format, output_dir) 排序的 TargetRecord,哈希覆盖 format/output_dir/file_template/options)。schema 未变而 target 配置变动 时:
- 指纹变化的 target 只重生成自己的产物(生成输入 = 完整文档——target 的字节取决于它序列化的每一张表);未变的 target 保持第二层的表级 传播与携带;
- 从 profile 移除的 target:其磁盘上的旧产物删除(只删 prev manifest 记录过的路径,且在生成成功之后),旧 output_dir 迁移同理;file_template 改名产生的换路径旧产物一并清理;
- identity 是
(format, output_dir)——两个 target 共用 output_dir 时 保守地一起重生成; - 合并后的 manifest 仍与全量构建逐字节一致(确定性契约)。
回退全量的条件:prev 无 table_hashes(旧版 manifest)、删除过表(prev 表集合 ⊄ 当前 —— 避免把过期产物错误携带)、携带产物读失败(warn 后 回退全量)、schema 变化。携带产物缺文件不算回退:缺文件的表在计算受 影响集合前已视为变更,随受影响表定向重生成。
例如 Item.xlsx 修改,影响:
Item
|
+--> DropTable
|
+--> Monster那么 Skill / Quest / Map 无需重新生成:
Changed Sources
|
v
table_hashes 比对 → 变更表
|
v
Dependency Graph 传播 → 受影响表
|
v
只重建受影响表 + 携带其余(字节一致)drop-table → full fallback 语义:table_hashes 的键是上一次构建的全部 表,当前 schema 若少了任何表(删除场景),第二层整体回退全量重建,避免 把过期产物错误携带。
Configuration Snapshot(v0.3 实装)
构建产物不止零散 JSON:cage snapshot 把 profile 视图打包成可独立 加载、自带校验的 Configuration Snapshot(八个概念 #7,格式定义在 cage_core::snapshot):
build/snapshot/<profile>[-<env>]-<build_id[..12]>/
├── manifest.json # 构建账本(profile / hashes / artifacts,与 build 逐字节一致;--env 时记 environment)
├── schema.json # profile 视图的规范 schema(构建所依据的形态;--env 时即 resolved 形态)
├── data/… # 数据类产物(json / csv / msgpack)
├── generated/… # 代码类产物
└── HASHES.json # 逐文件 blake3 账本(trust root,不自我哈希)+ build_id/content_hash与规划草稿的差异:目录名用 profile + build_id 指纹而非日期——时间戳 命名会破坏确定性构建契约(同输入 → 同快照字节、同名目录,重建即覆盖)。 数据/代码产物按 target format 分区,路径剥掉 output_dir 前缀、保留目标 子目录(build/client/Item.json → data/client/Item.json)。--env 出环境化包:目录名带 env 段(client-prod-<id>),包内 manifest 记 environment、schema.json 即该环境的 resolved 规则。
使用:
cage snapshot <project> --profile <p> # 构建 + 打包 + 自校验
cage snapshot <project> --env prod # 环境化出包(env_overrides 先解析再打包)
cage snapshot <snapshot-dir> --verify # 载入前校验(内核入口同服务器)服务器启动流程(cage_core::snapshot::{verify_snapshot, load}):
server
↓
verify HASHES.json(逐文件重哈希比对,缺/错/多文件均报)
↓
load(manifest + schema + data/generated 产物全量读入映射)
↓
load configuration严格性说明:逐文件账本比对的是「文件即账本、账本即文件」的完整一致性; content_hash 已涵盖产物整体指纹,作为账本中的交叉字段带上。整目录的 值传递:篡改任一文件、增删任一文件都会在校验时暴露。
快照自带校验信息、不依赖构建机现场。它与 runtime 侧的 Configuration Registry 分工明确:前者管产物 形态,后者管多版本分发与回滚。
Configuration Registry(R1–R4 已实装,design §29)
注册表把上面的可独立加载快照升级为多版本配置仓库:同一包可共存多 个版本的自校验快照(<registry>/<包>/<版本>/ 即一枚 Snapshot,逐文件 blake3 账本),包目录附确定性 index.json(版本序 / build_id / content_hash / 文件数),消费方经 registry:<包>[@<版本>] 载入条目——「未经校验不入册、未经校验不载入」。
首签 R1 本地发布/解析后,R 系列持续推进:
- R2
[dependencies]版本 pin(比较符区间 /^/~)与schema_path: registry:解析; - R3 远程 http(s) 根只读解析(匿名 GET 三资源 + 项目内
.cage-cache缓存、离线复用); - R4 全册
verify审计、滚动窗口gc、显式remove与同包重发 (幂等判定按整包指纹build_id + content_hash——schema/环境变了 就是不同的包,同版本重发报 E1801)。
命令、配置句法与回滚纪律见 CLI:registry; 协议与错误码见 design §29 与 validation.md E180x。
CI/CD
Cage 应该天然适合 CI:
Git Push
|
v
CI
|
v
cage check
|
+---- Error ---> Build Failed
|
v
cage build
|
v
Artifacts
|
v
Package / Deploy例如:
cage check --profile client
cage check --profile server
cage build --profile client
cage build --profile server配置错误在 CI 里先失败,不带进游戏。本仓库自身由 CI 门禁(全测试绿)与每日构建守护,warnings_as_errors 策略见 Validation:Warning Policy。