AGC 客户端随包资源 staging 导致 Cargo 构建永久失效(Windows/macOS 每次重编、macOS dev 无限重建) #519

Open
opened 2026-09-27 16:49:24 +08:00 by suzmii · 1 comment
Member

现象

  1. 主 crate 每次构建都重编:src-tauri 下连续两次 cargo build --no-default-features 分别为 41 / 48 / 65 / 79 秒,从不变 fresh(正常应为秒级 Finished)。IDE 的 cargo check --all-targets、Windows 本地 cargo test --no-run 同样重复构建。
  2. macOS 上 npm run agc 起不来:日志反复刷 Info File src-tauri/resources/... changed. Rebuilding application... + Running DevCommand (cargo run ...),实测一轮 28 次以上且不收敛;配套后端与 Vite 都已 ready,只有客户端壳反复重启。
  3. 并发重建时直接中断:thread 'main' panicked at build.rs ... 清理 macOS Codex staging 失败: Os { code: 66, kind: DirectoryNotEmpty },进程树退出。
  4. Linux 不受影响(五条 staging 在 Linux 上全为 no-op)。

根因(已定位到源码级)

apps/ai-game-creator-shell/src-tauri/build.rs 的 main() 前段依次执行五条 staging(Codex 双架构、Unity helper、Godot gdextension、插件工作区、Cocos payload),全部写进 crate 内的 src-tauri/resources/**。

而 tauri-build 会对 bundle.resources 里的每个文件发 cargo:rerun-if-changed,并把它拷进 target:

// tauri-build-2.6.3/src/lib.rs:88-93
for resource in resources.iter() {
    println!("cargo:rerun-if-changed={}", resource.path().display());
    ...
}

三份 tauri 配置把 resources/codex/** 与 resources/plugins 列进了随包资源(tauri.windows.conf.json:7-15、tauri.macos.conf.json:8-22;基线配置故意不含)。

于是构建期写的文件与 build script 声明的输入是同一批:

staging 写 resources/**  →  tauri-build 把同一批文件登记成输入  →  mtime 变化
        ↑                                                              ↓
        └──────────────  cargo 判 build script stale,重跑脚本  ←──────┘

判定依据是「输入文件 mtime 比 build script 输出新」,与目录位置无关:把 staging 搬到 target/ 也躲不开,因为登记的是配置里写的那些路径。关键不是「产物放哪」,而是「构建期有没有人写这些被登记的文件」。

证据

  • CARGO_LOG=cargo::core::compiler::fingerprint=info cargo build --no-default-features → stale: changed "resources/codex/mac-native/darwin-arm64/NOTICE.md",并给出 FsStatusOutdated(StaleItem(ChangedFile { reference: build/.../output, stale: .../NOTICE.md }))。
  • .fingerprint/**/run-build-script-build-script-build.json 的 RerunIfChanged.paths 里含 resources/plugins/agc-cocos-editor/panels/cocos-editor.html 等 staging 目标路径。
  • 反向验证:把「整目录删除重建 + 无条件重写」改成「只清残留 + 内容比对后才写」后,连续两次 cargo build 第二次 0.25 秒 fresh;npm run agc 108 秒后 app 与 --agent-runner 子进程起来,Rebuilding application 由 28+ 降为 0 次(该改动作为症状层验证,不计划合入,见下)。
  • Linux 不受影响的原因:插件 staging 对非 Windows/macOS 直接 return(build.rs:465-467),Codex 布局表没有 Linux 三元(for_target("x86_64-unknown-linux-gnu") == None,已有单测断言)。

为什么之前两轮修复没有收口

commit 修的是什么 为什么不够
299877b19(2026-09-11) 插件资源内容比对 + .taurignore .taurignore 只挡 dev 监听、挡不住 cargo 判定;且只覆盖 resources/plugins,resources/codex 一直没进
710d6dddf(2026-09-23,PR #487) Windows 上权限一致就不写元数据 写入侧单点修补;未处理「整目录删除 + 全量重写」
6f012d419(2026-09-18) — 给插件 staging 加了 remove_dir_all(destination_root),把上一条内容比对的幂等又抹掉
现状残留 — plugin.json(copy_plugin_file)、Cocos payload、Godot 产物三处仍是无条件写

结论:只要 build script 继续写受跟踪路径,就必须为每一处写入维护「无改动不写」,成本随写入点增长。这是结构问题,不是疏漏。

方案方向(待评审)

技术方案:docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md(同批提交含三份里程碑规范与首段实施计划)

  • 随包资源改由准备步骤生成:在 tauri dev|build 之前一次性 staging;缓存 key = 上游 lockfile resolved + integrity + 布局版本 + 目标三元;临时目录 + 原子替换、所有权与符号链接校验、幂等(命中缓存不重写任何文件)、并发串行化。
  • build.rs 退化为只读校验:校验平台目录存在、清单 schema/平台/版本、逐文件 sha256、必需组件齐全,失败 fail closed;只声明真实输入,不再声明任何生成物。
  • 落地后删除症状层补丁:残留清理逻辑、为幂等而设的辅助常量、.taurignore 中与随包资源相关的条目。
  • 已有先例可复用:Node 运行时由 scripts/stage-node-runtime.mjs 在发布前一次性 staging(build-release.mjs:21,427-429),从未进过 build script,因此从无此问题。

验收判据(摘)

  1. 源码不变时连续两次构建第二次为秒级 Finished;cargo:rerun-if-changed 输出与 fingerprint 记录中不再出现 src-tauri/resources/**。
  2. macOS 与 Windows 各一次 npm run agc 一次成功:Rebuilding application 为 0 次、Running DevCommand 为 1 次,客户端与 Runner 进程稳定存活。
  3. 包内资源与迁移前逐项一致(check-macos-bundle.mjs 全绿;Windows 安装包内 coding-agent、plugins、game-runtime/node 内容一致)。
  4. 上游缺失、摘要不匹配、清单缺组件、目标平台不支持四类场景各自 fail closed。

影响面

  • Windows/macOS 本地 dev:每次构建白等 40–90 秒;macOS 上客户端直接不可用。
  • IDE(cargo check --all-targets)与 Windows 本地 Rust 门禁同样重复构建。
  • 不受影响:Linux CI(不产这些资源)。

相关但不属于本问题

同一条 dev 入口还有一个独立缺陷已修:AGC 客户端 dev 的 Tauri Cargo 直接继承启动器环境,用户级 ~/.cargo/config.toml 的 rustc-wrapper(本机为 sccache)会生效,daemon 状态一坏就在首个 rustc 探测失败(failed to run rustc to learn about target-specific information)。修法是让 start-tauri-dev.mjs 复用 scripts/dev.mjs 的 buildLocalRustProcessEnv,与 npm run dev 口径一致;与本次 staging 归位方案互不覆盖,另行提交。

## 现象 1. **主 crate 每次构建都重编**:`src-tauri` 下连续两次 `cargo build --no-default-features` 分别为 41 / 48 / 65 / 79 秒,从不变 fresh(正常应为秒级 `Finished`)。IDE 的 `cargo check --all-targets`、Windows 本地 `cargo test --no-run` 同样重复构建。 2. **macOS 上 `npm run agc` 起不来**:日志反复刷 `Info File src-tauri/resources/... changed. Rebuilding application...` + `Running DevCommand (cargo run ...)`,实测一轮 28 次以上且不收敛;配套后端与 Vite 都已 ready,只有客户端壳反复重启。 3. **并发重建时直接中断**:`thread 'main' panicked at build.rs ... 清理 macOS Codex staging 失败: Os { code: 66, kind: DirectoryNotEmpty }`,进程树退出。 4. Linux 不受影响(五条 staging 在 Linux 上全为 no-op)。 ## 根因(已定位到源码级) `apps/ai-game-creator-shell/src-tauri/build.rs` 的 `main()` 前段依次执行五条 staging(Codex 双架构、Unity helper、Godot gdextension、插件工作区、Cocos payload),全部写进 crate 内的 `src-tauri/resources/**`。 而 `tauri-build` 会对 `bundle.resources` 里的**每个文件**发 `cargo:rerun-if-changed`,并把它拷进 target: ```rust // tauri-build-2.6.3/src/lib.rs:88-93 for resource in resources.iter() { println!("cargo:rerun-if-changed={}", resource.path().display()); ... } ``` 三份 tauri 配置把 `resources/codex/**` 与 `resources/plugins` 列进了随包资源(`tauri.windows.conf.json:7-15`、`tauri.macos.conf.json:8-22`;基线配置故意不含)。 于是**构建期写的文件**与 **build script 声明的输入**是同一批: ```text staging 写 resources/** → tauri-build 把同一批文件登记成输入 → mtime 变化 ↑ ↓ └────────────── cargo 判 build script stale,重跑脚本 ←──────┘ ``` 判定依据是「输入文件 mtime 比 build script 输出新」,**与目录位置无关**:把 staging 搬到 `target/` 也躲不开,因为登记的是配置里写的那些路径。关键不是「产物放哪」,而是「构建期有没有人写这些被登记的文件」。 ## 证据 - `CARGO_LOG=cargo::core::compiler::fingerprint=info cargo build --no-default-features` → `stale: changed "resources/codex/mac-native/darwin-arm64/NOTICE.md"`,并给出 `FsStatusOutdated(StaleItem(ChangedFile { reference: build/.../output, stale: .../NOTICE.md }))`。 - `.fingerprint/**/run-build-script-build-script-build.json` 的 `RerunIfChanged.paths` 里含 `resources/plugins/agc-cocos-editor/panels/cocos-editor.html` 等 staging 目标路径。 - 反向验证:把「整目录删除重建 + 无条件重写」改成「只清残留 + 内容比对后才写」后,连续两次 `cargo build` 第二次 **0.25 秒 fresh**;`npm run agc` **108 秒**后 app 与 `--agent-runner` 子进程起来,`Rebuilding application` 由 28+ 降为 **0** 次(该改动作为症状层验证,不计划合入,见下)。 - Linux 不受影响的原因:插件 staging 对非 Windows/macOS 直接 return(`build.rs:465-467`),Codex 布局表没有 Linux 三元(`for_target("x86_64-unknown-linux-gnu") == None`,已有单测断言)。 ## 为什么之前两轮修复没有收口 | commit | 修的是什么 | 为什么不够 | | --- | --- | --- | | `299877b19`(2026-09-11) | 插件资源内容比对 + `.taurignore` | `.taurignore` 只挡 dev 监听、挡不住 cargo 判定;且只覆盖 `resources/plugins`,`resources/codex` 一直没进 | | `710d6dddf`(2026-09-23,PR #487) | Windows 上权限一致就不写元数据 | 写入侧单点修补;未处理「整目录删除 + 全量重写」 | | `6f012d419`(2026-09-18) | — | 给插件 staging 加了 `remove_dir_all(destination_root)`,把上一条内容比对的幂等又抹掉 | | 现状残留 | — | `plugin.json`(`copy_plugin_file`)、Cocos payload、Godot 产物三处仍是无条件写 | 结论:只要 build script 继续写受跟踪路径,就必须为**每一处写入**维护「无改动不写」,成本随写入点增长。这是结构问题,不是疏漏。 ## 方案方向(待评审) 技术方案:`docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md`(同批提交含三份里程碑规范与首段实施计划) - **随包资源改由准备步骤生成**:在 `tauri dev|build` 之前一次性 staging;缓存 key = 上游 lockfile `resolved` + `integrity` + 布局版本 + 目标三元;临时目录 + 原子替换、所有权与符号链接校验、幂等(命中缓存不重写任何文件)、并发串行化。 - **`build.rs` 退化为只读校验**:校验平台目录存在、清单 schema/平台/版本、逐文件 sha256、必需组件齐全,失败 fail closed;只声明真实输入,不再声明任何生成物。 - **落地后删除症状层补丁**:残留清理逻辑、为幂等而设的辅助常量、`.taurignore` 中与随包资源相关的条目。 - 已有先例可复用:Node 运行时由 `scripts/stage-node-runtime.mjs` 在发布前一次性 staging(`build-release.mjs:21,427-429`),从未进过 build script,因此从无此问题。 ## 验收判据(摘) 1. 源码不变时连续两次构建第二次为秒级 `Finished`;`cargo:rerun-if-changed` 输出与 fingerprint 记录中不再出现 `src-tauri/resources/**`。 2. macOS 与 Windows 各一次 `npm run agc` 一次成功:`Rebuilding application` 为 0 次、`Running DevCommand` 为 1 次,客户端与 Runner 进程稳定存活。 3. 包内资源与迁移前逐项一致(`check-macos-bundle.mjs` 全绿;Windows 安装包内 `coding-agent`、`plugins`、`game-runtime/node` 内容一致)。 4. 上游缺失、摘要不匹配、清单缺组件、目标平台不支持四类场景各自 fail closed。 ## 影响面 - Windows/macOS 本地 dev:每次构建白等 40–90 秒;macOS 上客户端直接不可用。 - IDE(`cargo check --all-targets`)与 Windows 本地 Rust 门禁同样重复构建。 - 不受影响:Linux CI(不产这些资源)。 ## 相关但不属于本问题 同一条 dev 入口还有一个独立缺陷已修:AGC 客户端 dev 的 Tauri Cargo 直接继承启动器环境,用户级 `~/.cargo/config.toml` 的 `rustc-wrapper`(本机为 sccache)会生效,daemon 状态一坏就在首个 rustc 探测失败(`failed to run rustc to learn about target-specific information`)。修法是让 `start-tauri-dev.mjs` 复用 `scripts/dev.mjs` 的 `buildLocalRustProcessEnv`,与 `npm run dev` 口径一致;与本次 staging 归位方案互不覆盖,另行提交。
suzmii added the Kind/Bug
Priority
High
2
Reviewed
Confirmed
1
labels 2026-09-27 16:49:24 +08:00
suzmii self-assigned this 2026-09-27 16:50:59 +08:00
Author
Member

进展:M1 + M2 已交付(分支 fix/agc-rust-staging-idempotency,PR #520)

M1(单一声明 + 准备步骤 + 构建脚本只读校验)与 M2(dev/发布入口接线 + 构建脚本退出写入)已提交并推送。本机 macOS 实测:

  • cargo build --no-default-features 连续三次 0.69 / 0.22 / 0.22 秒全程 fresh(改造前每次 41–87 秒且从不 fresh)。
  • 强制构建脚本重跑后 resources/codex 与 resources/plugins 快照(相对路径/大小/mtime/sha256)逐项不变——构建期不再写随包资源。
  • tauri dev 真实 app 全程 Rebuilding application 0 次、Running DevCommand 1 次、主 crate 仅编译 1 次(改造前 28+ 次重建后仍起不来)。
  • 用例:准备步骤 9 passed、package_layout 36 passed、build-release 39 passed、dev 入口 12 passed;shell typecheck 链、check-config、check:encoding、check:doc-index、rustfmt、eslint、prettier、git diff --check 全绿。

剩余(M3,需 Windows 真机验收):Unity publish 目录、Godot gdextension、Cocos payload 仍是构建期写入,Windows 的构建新鲜度与打包一致性要在它们归位后复验;.taurignore 的 staging 条目同时保留到那时(提前删会在 Windows 上重新引入监听自触发)。

另:完整 npm run agc 在本机被 SpacetimeDB Pre-publish check(先 401 InvalidSignature、后 502 Bad Gateway,属既有本机环境问题)阻断,dev 侧改用等价探针(手工准备步骤 + 直接 tauri dev)取证。

既有失败(与本改动无关):bash scripts/check-repository-ci.sh origin/master 失败于 scripts/git-hooks.test.mjs:246「hook fixtures do not mutate the calling linked worktree or its index」,在干净的 origin/master 独立 worktree 上同样复现。

## 进展:M1 + M2 已交付(分支 `fix/agc-rust-staging-idempotency`,PR #520) M1(单一声明 + 准备步骤 + 构建脚本只读校验)与 M2(dev/发布入口接线 + 构建脚本退出写入)已提交并推送。本机 macOS 实测: - `cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒全程 fresh(改造前每次 41–87 秒且从不 fresh)。 - 强制构建脚本重跑后 `resources/codex` 与 `resources/plugins` 快照(相对路径/大小/mtime/sha256)逐项不变——构建期不再写随包资源。 - `tauri dev` 真实 app 全程 `Rebuilding application` 0 次、`Running DevCommand` 1 次、主 crate 仅编译 1 次(改造前 28+ 次重建后仍起不来)。 - 用例:准备步骤 9 passed、`package_layout` 36 passed、`build-release` 39 passed、dev 入口 12 passed;shell typecheck 链、`check-config`、`check:encoding`、`check:doc-index`、rustfmt、eslint、prettier、`git diff --check` 全绿。 剩余(M3,需 Windows 真机验收):Unity publish 目录、Godot gdextension、Cocos payload 仍是构建期写入,Windows 的构建新鲜度与打包一致性要在它们归位后复验;`.taurignore` 的 staging 条目同时保留到那时(提前删会在 Windows 上重新引入监听自触发)。 另:完整 `npm run agc` 在本机被 SpacetimeDB `Pre-publish check`(先 401 InvalidSignature、后 502 Bad Gateway,属既有本机环境问题)阻断,dev 侧改用等价探针(手工准备步骤 + 直接 `tauri dev`)取证。 既有失败(与本改动无关):`bash scripts/check-repository-ci.sh origin/master` 失败于 `scripts/git-hooks.test.mjs:246`「hook fixtures do not mutate the calling linked worktree or its index」,在干净的 `origin/master` 独立 worktree 上同样复现。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#519