Merge remote-tracking branch 'origin/master' into feat/agc-run-preview-tip

This commit is contained in:
2026-09-22 15:26:54 +08:00
8 changed files with 159 additions and 33 deletions
+39
View File
@@ -129,12 +129,19 @@ jobs:
sleep $((attempt * 2))
done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell Rust shard 3/4
run: npm run check:native-shells:agc-rust-shard-3
- name: Run AI game creator shell Rust shard 4/4
run: npm run check:native-shells:agc-rust-shard-4
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
# agent-run smoke 会 spawn `cargo run`(走壳自己的 manifest),同样不装 npm 依赖,
# 单独一个 job,免得把已经压到 4 分钟级的片 job 拖长。
ai-game-creator-shell-rust-smoke:
@@ -167,9 +174,16 @@ jobs:
sleep $((attempt * 2))
done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
# AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。
ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates
@@ -268,9 +282,16 @@ jobs:
sleep $((attempt * 2))
done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
backend-tests:
name: Backend tests
runs-on: genarrative-ci
@@ -341,6 +362,9 @@ jobs:
- name: Check server-rs boundaries
run: npm run check:server-rs-ddd
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run server-rs workspace tests
run: cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
@@ -353,6 +377,10 @@ jobs:
- name: Check SpacetimeDB module
run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
# 客户端的壳级与契约门禁:静态契约断言、H5 / 微信 / 移动 / 桌面壳运行时门禁,
# 以及依赖发布产物的构建 smoke。
native-shell-tests:
@@ -395,15 +423,26 @@ jobs:
- name: Run native shell contract gates
run: npm run check:native-shells:contract
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run native shell gates
run: npm run check:native-shells:shells
- name: Run native shell release build smoke
# 发布构建有独立 profile/features 和资源 staging,不消费测试对象快照。
env:
RUSTC_WRAPPER: ''
CARGO_BUILD_RUSTC_WRAPPER: ''
run: npm run check:native-shells:release
- name: Ensure native lockfiles are unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
frontend-tests:
name: Frontend tests
runs-on: genarrative-ci
+6 -6
View File
@@ -95,11 +95,11 @@ runner 配置保留原 `ubuntu-latest` 映射,`genarrative-ci` 继续映射到
回滚时先把 workflow 的 `runs-on` 改回 `ubuntu-latest`,再恢复备份的 runner config 并用同一超时重启 runner。不要在真实 CI 验证前删除旧映射或旧镜像。
### 可选的 AGC Rust 编译对象快照
### Rust 测试组编译对象快照
`scripts/build-gitea-rust-cache.sh` 在已验证的 job 镜像上生成候选镜像, AGC Rust lane 1 试验;lane 2 保持直接 rustc。固定 sccache `0.18.0` 的 Linux x64 musl 归档并校验 SHA-256,维护者从 origin/master 的确定提交预热编译对象,PR job 没有生成/发布公共快照的权限。
`scripts/build-gitea-rust-cache.sh` 在已验证的 job 镜像上生成候选镜像,覆盖 AGC Rust 两条 lane、crates、agent-run smoke、Backend 和 Native shell 的桌面壳测试。Native shell 的 release build smoke 显式清空两个 wrapper,不消费测试快照。固定 sccache `0.18.0` 的 Linux x64 musl 归档并校验 SHA-256,维护者从 origin/master 的确定提交预热编译对象,PR job 没有生成/发布公共快照的权限。
基础镜像必须不含 `/opt/genarrative-ci/rust-cache`;脚本在拉取源码、下载工具和预热前执行只读、断网检查,发现已有对象快照就拒绝构建。不能在旧缓存镜像上删除目录再叠加新快照,删除操作不会释放旧镜像层。切换且真实 CI 验证通过后,人工定向清理更旧的缓存镜像与导出归档,保留当前版、一个回滚版及运行中 CI 使用的版本;2 GiB 对象缓存上限不覆盖这些宿主文件,不使用全局 prune。
基础镜像必须不含 `/opt/genarrative-ci/rust-cache`;脚本在拉取源码、下载工具和预热前执行只读、断网检查,发现已有对象快照就拒绝构建。不能在旧缓存镜像上删除目录再叠加新快照,删除操作不会释放旧镜像层。切换且真实 CI 验证通过后,人工定向清理更旧的缓存镜像与导出归档,保留当前版、一个回滚版及运行中 CI 使用的版本;对象缓存上限不覆盖这些宿主文件,不使用全局 prune。
```bash
bash scripts/build-gitea-rust-cache.sh genarrative/gitea-project-ci:20260920.2 genarrative/gitea-project-ci:rust-cache-candidate
@@ -110,11 +110,11 @@ bash scripts/gitea-ci-job-image.sh load-runner genarrative/gitea-project-ci:rust
预热容器上限为 4 核、12 GiB,移除 capabilities,不挂宿主目录/socket,也不注入 Git/OSS/Jenkins 凭据。源码通过 `git archive` 复制,当前工作区、ignored 文件和 `.git` 不进入容器。最终从原镜像重新组装,仅复制 `/opt/genarrative-ci/rust-cache` 的 sccache、对象和来源元数据,不提交含源码/target 的预热容器;镜像本身的下载缓存与工具链校验保持原样。
快照由固定 Image ID 分发,每个 job 仅修改容器自己的写时复制层,缓存上限 2 GiB,结束后不回传。`ci-rust-cache.sh prepare` 清空继承的 `SCCACHE_*` 远程配置,使用独立配置和 Unix socket;旧镜像、工具链不匹配或限时 wrapper 探测失败时使用直接 rustc,正式编译启用 sccache 的 server IO 错误回退。真实编译/测试失败保留非零退出码。`report` 输出命中统计并停止本 job daemon,分片日志输出独立编译耗时。
快照由固定 Image ID 分发,每个 job 仅修改容器自己的写时复制层,缓存上限 4 GiB构建末尾输出实际对象体积,结束后不回传。`ci-rust-cache.sh prepare` 清空继承的 `SCCACHE_*` 远程配置,使用独立配置和 Unix socket;旧镜像、工具链不匹配或限时 wrapper 探测失败时使用直接 rustc,正式编译启用 sccache 的 server IO 错误回退。真实编译/测试失败保留非零退出码。`report` 输出命中统计并停止本 job daemon,分片日志输出独立编译耗时。sccache 0.18.0 的只读模式在 miss 后仍打包再拒绝写入,不能用它宣称零 miss 开销;普通 CI 继续只向容器层写入。
生成候选不会改变 runner 配置。线上有活跃 CI 时禁止停止 job、重启 runner 或切换标签;只在确认空闲后按上节流程切换固定 Image ID。先用相同源码、独立干净 target 比较无缓存、冷缓存、热缓存,真实 CI 验收通过后再考虑推广。回滚缓存试验可以移除 lane 1 的 prepare/report,或恢复镜像 ID,均不需要改变 incremental 或测试分片。
生成候选不会改变 runner 配置。线上有活跃 CI 时禁止停止 job、重启 runner 或切换标签;只在确认空闲后按上节流程切换固定 Image ID。全组启用须重建完整快照;旧 AGC 单目标快照不能作为其它组预热成功的证据。按组用相同源码、独立干净 target 比较编译命中与耗时;分片测试内容和前置检查不同,不能直接用两条 lane 总耗时推断缓存收益。回滚时可移除各 job 的 prepare/report,或恢复无对象缓存的原基础镜像 ID,均不需要改变 incremental 或测试分片。
预热路径固定为实际 Gitea checkout 的 `/workspace/GenarrativeAI/Genarrative`CargoAGC `src-tauri` 目录启动;`workspace.txt` 不匹配时直接编译。Rust cache key 对 cwd 敏感,不能假定 `SCCACHE_BASEDIRS` 足以跨路径复用。固定 sccache `0.18.0` 返回基础设施错误码 `2` 时 wrapper 只回退这一次 rustc 调用,其它状态原样返回,不重跑测试。
预热路径固定为实际 Gitea checkout 的 `/workspace/GenarrativeAI/Genarrative`:后端、独立 crate、插件、桌面壳和提示词契约从仓库根目录启动 CargoAGC 分片从 `src-tauri` 启动;agent-run smoke 从 AGC App 根目录启动。切换 AGC cwd 前仅清理预热容器内的 AGC target,避免 Cargo 的 fresh 判断跳过新 cwd 的对象。后端 workspace 与 spacetime-module 保持分开编译,测试仅 `--no-run`smoke 仅 `cargo build``workspace.txt` 不匹配时直接编译。Rust cache key 对 cwd 敏感,不能假定 `SCCACHE_BASEDIRS` 足以跨路径复用。固定 sccache `0.18.0` 返回基础设施错误码 `2` 时 wrapper 只回退这一次 rustc 调用,其它状态原样返回,不重跑测试。
## 启动与验证
@@ -96,6 +96,6 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
## Gitea CI 依赖闭合
AGC Rust lane 1 试点镜像内可信 sccache 对象快照,lane 2 保持直接 rustc 作为对照。维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中生成快照,最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计。线上存在活跃 CI 时不得重启 runner 或切换标签;候选镜像和真实无缓存/冷/热对照验收见开发运维文档。
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace 与 spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档。
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2``lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate`Native shell tests` 预取桌面壳与 AGC 壳 manifest`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate`agent-runtime-core``agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module``spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm``shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
@@ -5958,4 +5958,5 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 处理:预热使用已核实的 Gitea 路径 `/workspace/GenarrativeAI/Genarrative`,并与分片运行器一样从 AGC `src-tauri` 启动 Cargo;保存 `workspace.txt`,路径不符时回退直接编译。wrapper 放在容器内固定路径,daemon 状态与 Unix socket 仍使用随机私有目录;预热环境与 workflow 的 Cargo 环境由定向契约测试核对。不要为命中率随意增加 `RUSTFLAGS`、改写源码路径或恢复共享可写 target。
- 验证:相同源码、资源上限和独立干净 target 下分别记录无缓存、冷缓存、热缓存的编译耗时和 hit/miss;只有真实热命中有净收益才切换候选镜像。PR 的写入始终留在 job 容器层,公共快照仍由可信维护流程生成。
- 统计:job 私有 daemon 设置 `SCCACHE_IDLE_TIMEOUT=0`,由 `report` 显式停止;最终测试 bin 的不可缓存编译或测试可能超过一分钟,短 idle timeout 会让 daemon 提前退出,结尾查询启动新 daemon 后误报零次请求。容器销毁仍会回收该 job 的全部进程。
- 磁盘:快照构建拒绝含 `/opt/genarrative-ci/rust-cache` 的基础镜像,始终从无对象缓存的镜像重建;容器内删除旧对象不能释放 Docker 底层。2 GiB 上限不涵盖宿主旧镜像及导出归档,切换验证后按运维文档人工保留当前版和一个回滚版,同时保护运行中 CI 使用的镜像。
- 磁盘:快照构建拒绝含 `/opt/genarrative-ci/rust-cache` 的基础镜像,始终从无对象缓存的镜像重建;容器内删除旧对象不能释放 Docker 底层。对象缓存容量上限不涵盖宿主旧镜像及导出归档,切换验证后按运维文档人工保留当前版和一个回滚版,同时保护运行中 CI 使用的镜像。
- 扩展:预热所有 Rust 测试组时保留各自 cwd、profile、features 和锁策略;同一临时 target 的 Cargo fresh 不代表不同 cwd 都已生成缓存键,AGC 提示词契约、分片和 smoke 切换入口前清理预热 target。不要把 workspace 与 spacetime-module 合并成一次编译;Native shell release step 清空双 wrapper,避免将测试缓存扩展成发布缓存。当前 sccache 0.18.0 的 READ_ONLY 在 miss 后仍打包产物并产生 cache write error,不适合用来承诺“未命中无开销”。
@@ -332,19 +332,19 @@ workflow 首次成功运行后,**不**把 Project CI 的 job 配成 Gitea `mas
master 日常交付必须禁止直接 push,只允许经 PR 在最近一次 Project CI 全绿后合并;本地 `pre-commit` 的 staged ESLint/Prettier 和 master `pre-push` 的 Repository checks parity 只用于提前发现问题,可被 `--no-verify` 绕过,不能充当服务端权威门禁。紧急直推白名单如需保留,应按人员和时限最小化,并要求执行同一 `npm run check:repository-ci <base> <head>` 后回读 push CI。
#### AGC Rust lane 1 的隔离编译缓存试验
#### Rust 测试组的隔离编译缓存
每次生成快照必须使用不含对象缓存的原始 CI 基础镜像;构建脚本在拉取源码和预热前拒绝已存在 `/opt/genarrative-ci/rust-cache` 的基础镜像,避免重复叠加不可释放的旧对象层。切换并完成真实 CI 验证后,按 `deploy/container/README.md` 的保留规则人工定向清理旧缓存镜像及归档,保护运行中 CI 使用的版本。
只在 `AI game creator shell Rust lane 1/2` 启用 sccachelane 2 保持直接 rustc。继续设置 `CARGO_INCREMENTAL=0`,不共享 target、不恢复 Actions 可写缓存。可信快照通过已有固定 Image ID 分发:镜像只增加固定版本的 sccache、编译对象和来源元数据,不包含源码、target 或凭据;容器写时复制层承接本 job 的新增对象,job 删除后丢弃,PR 没有 Docker API 或快照发布权限。该权限边界由 runner 基础设施保证,不能仅用 workflow 的分支条件替代。
AGC Rust 两条 lane、crates、agent-run smoke、Backend 和 Native shell 的桌面壳测试均启用 sccache。Native shell 的 release build smoke 显式清空两个 wrapper,保持发布构建原有 profile/features 与资源 staging;前端和 repository checks 不启用对象缓存。继续设置 `CARGO_INCREMENTAL=0`,不共享 target、不恢复 Actions 可写缓存。可信快照通过已有固定 Image ID 分发:镜像只增加固定版本的 sccache、编译对象和来源元数据,不包含源码、target 或凭据;容器写时复制层承接本 job 的新增对象,job 删除后丢弃,PR 没有 Docker API 或快照发布权限。该权限边界由 runner 基础设施保证,不能仅用 workflow 的分支条件替代。
维护者在受信任 checkout 中运行 `bash scripts/build-gitea-rust-cache.sh <已验证基础镜像> <候选镜像tag>`。脚本从 origin 获取 master 的确定提交,在无宿主目录挂载、无凭据且有 CPU/内存上限的临时容器中编译 AGC bin 测试,仅导出 sccache 对象;最终镜像从原基础镜像重新组装,不提交预热容器。公共快照不接受 PR 上传,也不复用 Jenkins 发布缓存。工具链或系统依赖变化时重新生成快照,缓存命中仍由 sccache 的编译输入校验决定,不能省略 Cargo 构建。
维护者在受信任 checkout 中运行 `bash scripts/build-gitea-rust-cache.sh <已验证基础镜像> <候选镜像tag>`。脚本从 origin 获取 master 的确定提交,在无宿主目录挂载、无凭据且有 CPU/内存上限的临时容器中预热各组实际 Cargo 目标,只编译、不执行测试或应用。后端 workspace 仍排除 spacetime-module,再单独预热该模块,禁止为缓存合并 features。AGC 独立 crate 与插件沿用各自 manifest/锁文件策略;未提交锁文件的 crate 仍可能因新版本解析产生 miss。最终镜像从原基础镜像重新组装,只导出 sccache 对象,不提交预热容器。公共快照不接受 PR 上传,也不复用 Jenkins 发布缓存。工具链或系统依赖变化时重新生成快照,缓存命中仍由 sccache 的编译输入校验决定,不能省略 Cargo 构建。
lane 1 在编译前执行 `scripts/ci-rust-cache.sh prepare`:检查快照与 rustc 身份,隔离 sccache 配置和 daemon,限时探测真实 wrapper。旧镜像没有快照或探测失败时保留空 wrapper,输出 fallback 原因;缓存故障不得把真实编译/测试失败改成成功,也不允许重跑整个测试组掩盖失败。结束时 `report` 输出命中统计;分片日志单独记录 Cargo 编译耗时。对象缓存上限为 2 GiB,最终测试 bin 的链接仍须执行。
各 Rust job 在编译前执行 `scripts/ci-rust-cache.sh prepare`:检查快照与 rustc 身份,隔离 sccache 配置和 daemon,限时探测真实 wrapper。旧镜像没有快照或探测失败时保留空 wrapper,输出 fallback 原因;缓存故障不得把真实编译/测试失败改成成功,也不允许重跑整个测试组掩盖失败。结束时 `report` 输出命中统计;分片日志单独记录 Cargo 编译耗时。对象缓存上限为 4 GiB快照构建完成后输出实际体积;最终测试 bin 的链接仍须执行。全组开启时必须生成覆盖全部目标的新快照,不能把旧 AGC 单目标快照当作后端/桌面壳的预热验收。
候选镜像沿用 `gitea-ci-job-image.sh verify/export/load-runner` 验证和装载。现役 CI 正在运行时只允许准备候选镜像,不停止 job、不重启 runner、不切换标签;待确认无运行中的 job 后再按上述镜像更新顺序切换。验收分别记录独立 target 的无缓存、冷缓存和热缓存构建耗时及命中率,并检查失效/故障回退、两容器写入互不影响。未完成真实 CI 验证前不能宣称提速或推广到其它 job。
Rust 对象 key 包含编译工作目录。预热必须使用已核实的 Gitea checkout 路径 `/workspace/GenarrativeAI/Genarrative`,并像分片运行器一样从 `apps/ai-game-creator-shell/src-tauri` 启动 Cargo快照保存 `workspace.txt`job 根路径不符时直接编译,避免零命中的缓存开销。不要仅设置 `SCCACHE_BASEDIRS` 就假定 Rust 可以跨 cwd 命中,也不为缓存改写 `RUSTFLAGS` 或源码路径语义。固定 sccache `0.18.0` 的基础设施错误码 `2` 会由 wrapper 回退执行本次 rustc;其它退出码原样返回,升级 sccache/Rust 时须复核此约定。
Rust 对象 key 包含编译工作目录。预热必须使用已核实的 Gitea checkout 路径 `/workspace/GenarrativeAI/Genarrative`:后端、独立 crate、插件、桌面壳及提示词契约从仓库根目录启动 Cargo,AGC 分片从 `apps/ai-game-creator-shell/src-tauri` 启动,agent-run smoke 从 `apps/ai-game-creator-shell` 启动。切换 AGC 编译入口前清理该临时容器的 AGC target,防止 Cargo 的 fresh 判断跳过新 cwd 所需对象;CI 各 job 的 target 本来就独立。快照保存 `workspace.txt`,job 根路径不符时直接编译。不要仅设置 `SCCACHE_BASEDIRS` 就假定 Rust 可以跨 cwd 命中,也不为缓存改写 `RUSTFLAGS` 或源码路径语义。固定 sccache `0.18.0` 的基础设施错误码 `2` 会由 wrapper 回退执行本次 rustc;其它退出码原样返回,升级 sccache/Rust 时须复核此约定。
sccache `0.18.0` 还会 hash 大部分 `CARGO_*` 环境变量。因此 wrapper 固定在每个容器自己的 `/opt/genarrative-ci/rust-cache/rustc-wrapper`prepare 重写 launcher 后才启用;daemon 状态与 socket 仍各自随机隔离。预热同步 workflow 的 `CARGO_INCREMENTAL``CARGO_HTTP_MULTIPLEXING``CARGO_NET_RETRY``CARGO_TERM_COLOR`,由现有 CI 契约测试验证一致;新增 Cargo 环境配置时须同步评估 cache key,不能只看快照文件是否存在。
+43 -3
View File
@@ -63,9 +63,49 @@ source "${GITHUB_ENV}"
set +a
test -n "${RUSTC_WRAPPER}"
trap 'bash /tmp/ci-rust-cache.sh report' EXIT
cd apps/ai-game-creator-shell/src-tauri
cargo test --locked --manifest-path Cargo.toml \
--bin genarrative-ai-game-creator-shell --no-run
# 与 CI 的工作目录、features 和目标逐项对齐,只编译,不运行测试/应用。
echo '[rust-cache] warming backend tests and checks'
cargo test --locked --workspace --exclude spacetime-module --no-fail-fast \
--manifest-path server-rs/Cargo.toml --no-run
cargo test --locked -p spacetime-module --no-fail-fast \
--manifest-path server-rs/Cargo.toml --no-run
cargo check --locked -p api-server --all-targets --manifest-path server-rs/Cargo.toml
cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
echo '[rust-cache] warming AGC standalone, shared and plugin crates'
for manifest in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml \
plugins/agc-cocos-editor/native/cocos-editor-bridge/Cargo.toml; do
# 与 CI 一样,这些 crate 没有已提交的独立 Cargo.lock。
cargo test --manifest-path "${manifest}" --no-run
done
cargo test --locked -p platform-llm --manifest-path server-rs/Cargo.toml --no-run
cargo test --locked -p shared-contracts --manifest-path server-rs/Cargo.toml --no-run
for editor in unity godot; do
cargo test --locked --manifest-path "plugins/agc-${editor}-editor/native/${editor}-editor-bridge/Cargo.toml" --no-run
done
echo '[rust-cache] warming desktop tests and AGC prompt contracts'
cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml --no-run
cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml \
--test runtime_prompt_bundle_build --test prompt_source_boundaries --no-run
# Cargo 的 fresh 判断不保证不同 cwd 都经过 sccache;先清掉临时容器中的 target。
cargo clean --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml
echo '[rust-cache] warming AGC Rust lanes'
(
cd apps/ai-game-creator-shell/src-tauri
cargo test --locked --manifest-path Cargo.toml \
--bin genarrative-ai-game-creator-shell --no-run
)
cargo clean --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml
echo '[rust-cache] warming AGC agent-run smoke executable'
(
cd apps/ai-game-creator-shell
cargo build --manifest-path src-tauri/Cargo.toml
)
du -sh /opt/genarrative-ci/rust-cache/objects
WARM
docker cp "${container_id}:/opt/genarrative-ci/rust-cache/." "${work_dir}/snapshot/"
docker rm -f "${container_id}" >/dev/null
+1 -1
View File
@@ -12,7 +12,7 @@ configure_local_cache() {
for variable in ${!SCCACHE_@}; do unset "${variable}"; done
export SCCACHE_CONF="${state}/config"
export SCCACHE_DIR="${cache_root}/objects"
export SCCACHE_CACHE_SIZE=2G
export SCCACHE_CACHE_SIZE=4G
export SCCACHE_SERVER_UDS="${state}/server.sock"
# 单个不可缓存的链接/测试阶段可能超过一分钟;保持统计直到 report 显式停止。
export SCCACHE_IDLE_TIMEOUT=0
+62 -16
View File
@@ -142,23 +142,45 @@ function backendStepIndex(stepName: string) {
}
describe('project CI workflow', () => {
it('trials isolated compilation caching only in AGC Rust lane 1', () => {
const lane = jobSection('ai-game-creator-shell-rust-lane-1');
expect(lane).toContain('node --test scripts/ci-rust-cache.test.mjs');
expect(lane.indexOf('bash scripts/ci-rust-cache.sh prepare')).toBeLessThan(
lane.indexOf('npm run check:native-shells:agc-rust-shard-1'),
);
expect(
stepSection(
'ai-game-creator-shell-rust-lane-1',
'Report isolated Rust compilation cache',
),
).toContain('if: always()');
for (const job of jobNames.filter(
(name) => name !== 'ai-game-creator-shell-rust-lane-1',
)) {
expect(jobSection(job)).not.toContain('ci-rust-cache.sh');
it('uses isolated compilation caching for every Rust test job', () => {
const firstRustSteps = {
'ai-game-creator-shell-rust-lane-1':
'Run AI game creator shell Rust shard 1/4',
'ai-game-creator-shell-rust-lane-2':
'Run AI game creator shell Rust shard 3/4',
'ai-game-creator-shell-rust-smoke':
'Run AI game creator shell agent-run smoke',
'ai-game-creator-shell-rust-crates':
'Run AI game creator shell shared crate gates',
'backend-tests': 'Run server-rs workspace tests',
'native-shell-tests': 'Run native shell gates',
};
for (const job of jobNames) {
const section = jobSection(job);
if (!(job in firstRustSteps)) {
expect(section).not.toContain('ci-rust-cache.sh');
continue;
}
const firstRustStep = firstRustSteps[job as keyof typeof firstRustSteps];
const prepare = section.indexOf('bash scripts/ci-rust-cache.sh prepare');
expect(prepare).toBeGreaterThan(
section.indexOf('Checkout full history from Gitea'),
);
expect(prepare).toBeLessThan(section.indexOf(`- name: ${firstRustStep}`));
expect(
stepSection(job, 'Report isolated Rust compilation cache'),
).toContain('if: always()');
}
// 缓存自身的行为测试只执行一次,避免随 job 数量重复运行。
expect(
workflow.match(/node --test scripts\/ci-rust-cache.test.mjs/g),
).toHaveLength(1);
const releaseStep = stepSection(
'native-shell-tests',
'Run native shell release build smoke',
);
expect(releaseStep).toContain("RUSTC_WRAPPER: ''");
expect(releaseStep).toContain("CARGO_BUILD_RUSTC_WRAPPER: ''");
expect(workflow).toContain("CARGO_INCREMENTAL: '0'");
expect(workflow).toContain("RUSTC_WRAPPER: ''");
for (const [, name, value] of workflow
@@ -170,6 +192,30 @@ describe('project CI workflow', () => {
}
});
it('warms backend targets without merging the SpacetimeDB feature boundary', () => {
const warmCommands = rustCacheBuildScript
.replace(/\\\r?\n/g, '')
.replace(/\s+/g, ' ');
for (const [, command] of jobSection('backend-tests').matchAll(
/run: (cargo [^\n]+)/g,
)) {
expect(warmCommands).toContain(command.trim());
}
expect(warmCommands).toContain(
'cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml --no-run',
);
expect(warmCommands).toContain(
'cargo test --locked -p spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml --no-run',
);
expect(warmCommands).toContain('cd apps/ai-game-creator-shell/src-tauri');
expect(warmCommands).toContain(
'cd apps/ai-game-creator-shell cargo build --manifest-path src-tauri/Cargo.toml',
);
expect(warmCommands).toContain(
'cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml --no-run',
);
});
it('runs for master pushes, pull requests, and manual dispatch only', () => {
expect(workflow).toMatch(
/on:\n {2}push:\n {4}branches:\n {6}- master\n {2}pull_request:\n {2}workflow_dispatch:/u,