Skip to content

Build:确定性构建 ​

确定性是配置构建系统的硬要求:相同输入必须得到相同产物。

确定性构建 ​

相同:

text
Source
Schema
Cage Version
Profile

应该得到相同:

text
Artifact
Hash
Manifest

即:

text
Build(A) == Build(A)

避免:

  • 时间戳导致文件变化
  • 不确定 Map 顺序
  • 随机 ID
  • 非稳定排序

落地口径:IndexMap 保序 + 显式稳定排序(对象键经 BTreeMap 规范化),产物字节级可复现(golden 测试:同输入两次构建字节级一致),禁止时间戳与随机 ID 进入产物。

Build Manifest ​

每次构建生成 Manifest(cage-core::manifest,BuildManifest / ArtifactInfo):

json
{
  "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,含 环检测与拓扑排序)。例如:

text
Monster
   |
   | DropTableID
   v
DropTable
   |
   | ItemID
   v
Item

最终:

text
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 修改,影响:

text
Item
 |
 +--> DropTable
 |
 +--> Monster

那么 Skill / Quest / Map 无需重新生成:

text
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):

text
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 规则。

使用:

text
cage snapshot <project> --profile <p>   # 构建 + 打包 + 自校验
cage snapshot <project> --env prod      # 环境化出包(env_overrides 先解析再打包)
cage snapshot <snapshot-dir> --verify   # 载入前校验(内核入口同服务器)

服务器启动流程(cage_core::snapshot::{verify_snapshot, load}):

text
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:

text
Git Push
    |
    v
CI
    |
    v
cage check
    |
    +---- Error ---> Build Failed
    |
    v
cage build
    |
    v
Artifacts
    |
    v
Package / Deploy

例如:

bash
cage check --profile client
cage check --profile server

cage build --profile client
cage build --profile server

配置错误在 CI 里先失败,不带进游戏。本仓库自身由 CI 门禁(全测试绿)与每日构建守护,warnings_as_errors 策略见 Validation:Warning Policy。

基于 Apache-2.0 许可发布