From cea3e2340f05c1c7a356691a8df90a694868d46d Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 05:20:11 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=E6=94=AF=E6=8C=81=20Windows=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=20sccache=20=E8=87=AA=E5=8A=A8=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows 本地开发检测可用的 sccache 并启用缓存 未安装或不可执行时清空 Rust wrapper 并回退到 rustc 补充测试并更新开发运维文档 --- ...发运维】本地开发验证与生产运维-2026-05-15.md | 2 +- scripts/dev.mjs | 26 +++++++++++++++---- scripts/dev.test.ts | 11 ++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index a1bdae5e9..dea400b84 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -66,7 +66,7 @@ AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creato Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端的窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`,Windows 使用 `taskkill /PID /T /F`。Windows 下每个长驻服务都经 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 会先杀掉包装层(退出码 `0xC000013A`),因此清理不能只看直接子进程是否存活:`taskkill` 对已退出的 PID 只会失败,必须继续按记录下来的根 PID 遍历,并在退出时按本工作树 `api-server.exe` 绝对路径(以及本次自己拉起的 SpacetimeDB `--data-dir`)做一次身份兜底清扫;`scripts/dev-windows-process.mjs` 是这套判定的唯一实现。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc//stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。 -Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 会用空的 `RUSTC_WRAPPER` / `CARGO_BUILD_RUSTC_WRAPPER` 覆盖 `server-rs/.cargo/config.toml` 里的 `sccache`,从而直连真实 `rustc`。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志是否出现该错误,再确认脚本注入的 wrapper 为空。 +Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 会先探测 `sccache --version`:可用时设置 `RUSTC_WRAPPER` / `CARGO_BUILD_RUSTC_WRAPPER=sccache` 使用缓存;未安装或不可执行时设置为空值覆盖 `server-rs/.cargo/config.toml`,回退到真实 `rustc`,不阻断启动。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志中的 sccache 启用或回退提示。 ### 本地 Rust 构建缓存与磁盘上限 diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 964cba65b..f98b88810 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -146,9 +146,23 @@ function applyLocalFfmpegEnv(env, platform = process.platform) { return env; } -function resolveLocalDevRustcWrapperBypass() { - // Windows 下不能把 rustc 自身当成 Cargo wrapper;空值会覆盖仓库 .cargo/config.toml 中的 sccache。 - return process.platform === 'win32' ? '' : '/usr/bin/env'; +function resolveLocalDevRustcWrapperBypass(options = {}) { + if (process.platform !== 'win32') { + return '/usr/bin/env'; + } + + const sccacheAvailable = + options.sccacheAvailable ?? + (() => { + const result = spawnSync('sccache', ['--version'], { + cwd: repoRoot, + encoding: 'utf8', + shell: true, + windowsHide: true, + }); + return !result.error && result.status === 0; + })(); + return sccacheAvailable ? 'sccache' : ''; } const SERVICE_NAMES = [ @@ -585,12 +599,14 @@ function buildLocalRustProcessEnv(env, options = {}) { return mergedEnv; } - const rustcWrapperBypass = resolveLocalDevRustcWrapperBypass(); + const rustcWrapperBypass = resolveLocalDevRustcWrapperBypass(options); mergedEnv.RUSTC_WRAPPER = rustcWrapperBypass; mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapperBypass; if (options.log !== false) { console.warn( - '[dev:rust] 本地 dev 构建绕过项目 sccache wrapper,避免缓存进程异常阻断启动。', + rustcWrapperBypass + ? '[dev:rust] 本地 dev 构建启用 sccache wrapper。' + : '[dev:rust] 本地 dev 构建未找到可用 sccache,回退到直接 rustc。', ); } return mergedEnv; diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index fd932b81b..884e2fa72 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -657,17 +657,16 @@ describe('dev scheduler local worker cleanup', () => { }); describe('dev scheduler Rust build env', () => { - test('local dev Rust env bypasses project sccache wrapper', () => { + test('local dev Rust env enables available sccache wrapper', () => { const env = buildLocalRustProcessEnv( { RUSTC_WRAPPER: '/usr/bin/sccache', CARGO_BUILD_RUSTC_WRAPPER: 'sccache', }, - { log: false }, + { log: false, sccacheAvailable: true }, ); - expect(env.RUSTC_WRAPPER).not.toBe('/usr/bin/sccache'); - expect(env.RUSTC_WRAPPER).not.toBe('sccache'); + expect(env.RUSTC_WRAPPER).toBe('sccache'); expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(env.RUSTC_WRAPPER); }); @@ -684,7 +683,7 @@ describe('dev scheduler Rust build env', () => { expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('custom-wrapper'); }); - test('Windows 下本地 dev Rust env 用空 wrapper 覆盖项目 sccache', () => { + test('Windows 下未安装 sccache 时回退到空 wrapper', () => { const originalPlatform = Object.getOwnPropertyDescriptor( process, 'platform', @@ -700,7 +699,7 @@ describe('dev scheduler Rust build env', () => { RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'sccache', }, - { log: false }, + { log: false, sccacheAvailable: false }, ); expect(env.RUSTC_WRAPPER).toBe(''); -- 2.52.0 From 2315883b1f1f2c3befbc67815d703e1817a54f38 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 05:56:28 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20sccache=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=9A=84=E5=B9=B3=E5=8F=B0=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为启用 sccache 的用例补充 Windows platform mock 确保 POSIX 环境下测试不会误走 env wrapper 分支 --- scripts/dev.test.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 884e2fa72..4bf5cc82d 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -658,16 +658,31 @@ describe('dev scheduler local worker cleanup', () => { describe('dev scheduler Rust build env', () => { test('local dev Rust env enables available sccache wrapper', () => { - const env = buildLocalRustProcessEnv( - { - RUSTC_WRAPPER: '/usr/bin/sccache', - CARGO_BUILD_RUSTC_WRAPPER: 'sccache', - }, - { log: false, sccacheAvailable: true }, + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + 'platform', ); + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'win32', + }); - expect(env.RUSTC_WRAPPER).toBe('sccache'); - expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(env.RUSTC_WRAPPER); + try { + const env = buildLocalRustProcessEnv( + { + RUSTC_WRAPPER: '/usr/bin/sccache', + CARGO_BUILD_RUSTC_WRAPPER: 'sccache', + }, + { log: false, sccacheAvailable: true }, + ); + + expect(env.RUSTC_WRAPPER).toBe('sccache'); + expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(env.RUSTC_WRAPPER); + } finally { + if (originalPlatform) { + Object.defineProperty(process, 'platform', originalPlatform); + } + } }); test('local dev Rust env keeps healthy custom wrapper untouched', () => { -- 2.52.0 From e85c2161125bb7f98872da60176a6493dd16c290 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 06:02:23 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=20Windows=20sccache=20wr?= =?UTF-8?q?apper=20=E6=8E=A2=E6=B5=8B=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用限时真实 rustc wrapper 探测替代版本检查 探测失败或超时时回退到直接 rustc 同步更新 sccache 故障排障记录 --- docs/project-memory/shared-memory/pitfalls.md | 2 +- scripts/dev.mjs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 50e67a1b8..230c06b9d 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -2639,7 +2639,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`、`sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。 - 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。 -- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` 由 `scripts/dev.mjs` 给 Rust 子进程注入直通 wrapper,自动绕过项目默认 sccache,避免损坏的 daemon 阻断 `spacetime publish` 或 `api-server` 启动;显式设置的非 sccache 自定义 wrapper 会被保留。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 +- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` 在 Windows 下限时执行真实 wrapper 探测 `sccache rustc -vV`,成功才启用 sccache,缺少命令、daemon 启动超时或 wrapper 返回非零时立即给 Rust 子进程注入空 wrapper,回退到直接 rustc,避免损坏的 daemon 阻断启动;显式设置的非 sccache 自定义 wrapper 会被保留。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 - 验证:`rustc -Vv` 能输出版本;本地 `npm run dev` 能完成 `spacetime publish`、`api-server` `/healthz`、主站 Vite 和后台 Vite 启动;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明原始 Cargo/Jenkins 路径仍可使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。 - 关联:`scripts/dev.mjs`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。 diff --git a/scripts/dev.mjs b/scripts/dev.mjs index f98b88810..dfba21bc6 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -154,11 +154,12 @@ function resolveLocalDevRustcWrapperBypass(options = {}) { const sccacheAvailable = options.sccacheAvailable ?? (() => { - const result = spawnSync('sccache', ['--version'], { + const result = spawnSync('sccache', ['rustc', '-vV'], { cwd: repoRoot, encoding: 'utf8', shell: true, windowsHide: true, + timeout: options.sccacheProbeTimeoutMs ?? 10_000, }); return !result.error && result.status === 0; })(); -- 2.52.0 From 136756134a3d12224cad5fba33e274dfb6454840 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 06:19:38 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=E6=8C=89=E6=98=BE=E5=BC=8F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=8E=A7=E5=88=B6=20Windows=20sccache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未配置 wrapper 时默认回退到直接 rustc 处理 sccache 探测失败与 wrapper 配置冲突 同步更新开发运维和排障文档 --- docs/project-memory/shared-memory/pitfalls.md | 4 +- ...发运维】本地开发验证与生产运维-2026-05-15.md | 2 +- scripts/dev.mjs | 36 ++++++++++++------ scripts/dev.test.ts | 37 +++++++++++++++++-- 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 230c06b9d..9e6d2aad7 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1158,8 +1158,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 现象:Windows 上执行 `npm run dev:api-server` 时,api-server 在 Cargo 启动阶段失败,日志出现 `error: multiple input filenames provided (first two filenames are ... rustc.exe and -)`,`/healthz` 无法访问。 - 原因:`server-rs/.cargo/config.toml` 默认配置 `rustc-wrapper = "sccache"`;本地 dev 脚本为了绕过损坏的 sccache 需要覆盖 wrapper。Windows 下如果把 `RUSTC_WRAPPER` 设置为 `rustc`,Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,真实 rustc 把 wrapper 传入的 rustc 路径和 stdin `-` 都当输入文件。 -- 处理:Windows 本地 dev 脚本应把 `RUSTC_WRAPPER` 和 `CARGO_BUILD_RUSTC_WRAPPER` 显式设为空字符串,让 Cargo 覆盖项目配置并直连真实 rustc;Linux 保持 `/usr/bin/env` 绕过 sccache。 -- 验证:`npm run test -- scripts/dev.test.ts -t "Windows 下本地 dev Rust env 用空 wrapper 覆盖项目 sccache"`,并用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。 +- 处理:Windows 本地 dev 脚本默认把两个 wrapper 设为空字符串;只有用户显式配置 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 时才处理 sccache。sccache 配置会先限时执行真实 `sccache rustc -vV` 探测,失败、超时或两个变量冲突时回退到直接 rustc;Linux 保持 `/usr/bin/env` 绕过 sccache。 +- 验证:`npm run test -- scripts/dev.test.ts -t "dev scheduler Rust build env"`,并用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。 - 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 ## Pingora 直连 80/443 不能只改 env diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index dea400b84..1e9d54d60 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -66,7 +66,7 @@ AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creato Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端的窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`,Windows 使用 `taskkill /PID /T /F`。Windows 下每个长驻服务都经 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 会先杀掉包装层(退出码 `0xC000013A`),因此清理不能只看直接子进程是否存活:`taskkill` 对已退出的 PID 只会失败,必须继续按记录下来的根 PID 遍历,并在退出时按本工作树 `api-server.exe` 绝对路径(以及本次自己拉起的 SpacetimeDB `--data-dir`)做一次身份兜底清扫;`scripts/dev-windows-process.mjs` 是这套判定的唯一实现。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc//stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。 -Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 会先探测 `sccache --version`:可用时设置 `RUSTC_WRAPPER` / `CARGO_BUILD_RUSTC_WRAPPER=sccache` 使用缓存;未安装或不可执行时设置为空值覆盖 `server-rs/.cargo/config.toml`,回退到真实 `rustc`,不阻断启动。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志中的 sccache 启用或回退提示。 +Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 默认不主动启用 sccache;只有用户通过 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 显式配置 wrapper 时才进入处理流程。配置为 sccache 时会限时执行真实 wrapper 探测,成功才使用缓存;未安装、不可执行、超时或两个变量冲突时设置为空值,回退到真实 `rustc`,不阻断启动。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志中的 wrapper 启用、冲突或回退提示。 ### 本地 Rust 构建缓存与磁盘上限 diff --git a/scripts/dev.mjs b/scripts/dev.mjs index dfba21bc6..a1aefc2f3 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -591,23 +591,37 @@ function buildLocalRustProcessEnv(env, options = {}) { String(mergedEnv.RUSTC_WRAPPER ?? '').trim(), String(mergedEnv.CARGO_BUILD_RUSTC_WRAPPER ?? '').trim(), ].filter(Boolean); - const customWrapper = wrappers.find( - (wrapper) => !isSccacheRustcWrapper(wrapper), - ); - if (customWrapper) { - mergedEnv.RUSTC_WRAPPER = customWrapper; - mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = customWrapper; + const uniqueWrappers = [...new Set(wrappers)]; + if (uniqueWrappers.length > 1) { + mergedEnv.RUSTC_WRAPPER = ''; + mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = ''; + if (options.log !== false) { + console.warn( + '[dev:rust] RUSTC_WRAPPER 与 CARGO_BUILD_RUSTC_WRAPPER 配置冲突,回退到直接 rustc。', + ); + } return mergedEnv; } - const rustcWrapperBypass = resolveLocalDevRustcWrapperBypass(options); - mergedEnv.RUSTC_WRAPPER = rustcWrapperBypass; - mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapperBypass; + const configuredWrapper = uniqueWrappers[0] ?? ''; + if (configuredWrapper && !isSccacheRustcWrapper(configuredWrapper)) { + mergedEnv.RUSTC_WRAPPER = configuredWrapper; + mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = configuredWrapper; + return mergedEnv; + } + + const rustcWrapper = configuredWrapper + ? resolveLocalDevRustcWrapperBypass(options) + : ''; + mergedEnv.RUSTC_WRAPPER = rustcWrapper; + mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapper; if (options.log !== false) { console.warn( - rustcWrapperBypass + rustcWrapper ? '[dev:rust] 本地 dev 构建启用 sccache wrapper。' - : '[dev:rust] 本地 dev 构建未找到可用 sccache,回退到直接 rustc。', + : configuredWrapper + ? '[dev:rust] sccache wrapper 探测失败,回退到直接 rustc。' + : '[dev:rust] 未显式配置 Rust wrapper,使用直接 rustc。', ); } return mergedEnv; diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 4bf5cc82d..e7449cb5f 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -657,6 +657,28 @@ describe('dev scheduler local worker cleanup', () => { }); describe('dev scheduler Rust build env', () => { + test('Windows 下未显式配置 wrapper 时默认关闭 sccache', () => { + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + 'platform', + ); + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'win32', + }); + try { + const env = buildLocalRustProcessEnv( + {}, + { log: false, sccacheAvailable: true }, + ); + expect(env.RUSTC_WRAPPER).toBe(''); + expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(''); + } finally { + if (originalPlatform) + Object.defineProperty(process, 'platform', originalPlatform); + } + }); + test('local dev Rust env enables available sccache wrapper', () => { const originalPlatform = Object.getOwnPropertyDescriptor( process, @@ -670,7 +692,7 @@ describe('dev scheduler Rust build env', () => { try { const env = buildLocalRustProcessEnv( { - RUSTC_WRAPPER: '/usr/bin/sccache', + RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'sccache', }, { log: false, sccacheAvailable: true }, @@ -685,11 +707,11 @@ describe('dev scheduler Rust build env', () => { } }); - test('local dev Rust env keeps healthy custom wrapper untouched', () => { + test('Rust wrapper 配置为同一个自定义 wrapper 时保持不变', () => { const env = buildLocalRustProcessEnv( { RUSTC_WRAPPER: 'custom-wrapper', - CARGO_BUILD_RUSTC_WRAPPER: 'sccache', + CARGO_BUILD_RUSTC_WRAPPER: 'custom-wrapper', }, { log: false }, ); @@ -725,6 +747,15 @@ describe('dev scheduler Rust build env', () => { } } }); + + test('Rust wrapper 配置冲突时回退到空 wrapper', () => { + const env = buildLocalRustProcessEnv( + { RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'custom-wrapper' }, + { log: false }, + ); + expect(env.RUSTC_WRAPPER).toBe(''); + expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(''); + }); }); describe('dev scheduler stack state file', () => { -- 2.52.0 From 37743f66c47d36c71876aeb66b0b8a7cac061ca0 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 06:29:28 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20POSIX=20sccache=20?= =?UTF-8?q?=E7=BB=95=E8=BF=87=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明确记录 Linux 和 macOS 本地开发实际绕过 sccache 补充 POSIX 日志回归测试并更新排障文档 --- docs/project-memory/shared-memory/pitfalls.md | 2 +- scripts/dev.mjs | 16 ++++++++---- scripts/dev.test.ts | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 9494abd63..d5bde3b01 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1159,7 +1159,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 现象:Windows 上执行 `npm run dev:api-server` 时,api-server 在 Cargo 启动阶段失败,日志出现 `error: multiple input filenames provided (first two filenames are ... rustc.exe and -)`,`/healthz` 无法访问。 - 原因:`server-rs/.cargo/config.toml` 默认配置 `rustc-wrapper = "sccache"`;本地 dev 脚本为了绕过损坏的 sccache 需要覆盖 wrapper。Windows 下如果把 `RUSTC_WRAPPER` 设置为 `rustc`,Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,真实 rustc 把 wrapper 传入的 rustc 路径和 stdin `-` 都当输入文件。 - 处理:Windows 本地 dev 脚本默认把两个 wrapper 设为空字符串;只有用户显式配置 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 时才处理 sccache。sccache 配置会先限时执行真实 `sccache rustc -vV` 探测,失败、超时或两个变量冲突时回退到直接 rustc;Linux 保持 `/usr/bin/env` 绕过 sccache。 -- 验证:`npm run test -- scripts/dev.test.ts -t "dev scheduler Rust build env"`,并用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。 +- 验证:`npm run test -- scripts/dev.test.ts -t "dev scheduler Rust build env"`;POSIX 显式配置 sccache 时日志应明确说明绕过并使用直接 rustc;再用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。 - 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 ## Pingora 直连 80/443 不能只改 env diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 8656e44b3..62d4f2732 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -623,12 +623,18 @@ function buildLocalRustProcessEnv(env, options = {}) { mergedEnv.RUSTC_WRAPPER = rustcWrapper; mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapper; if (options.log !== false) { + const isPosixSccacheBypass = + configuredWrapper && + isSccacheRustcWrapper(configuredWrapper) && + process.platform !== 'win32'; console.warn( - rustcWrapper - ? '[dev:rust] 本地 dev 构建启用 sccache wrapper。' - : configuredWrapper - ? '[dev:rust] sccache wrapper 探测失败,回退到直接 rustc。' - : '[dev:rust] 未显式配置 Rust wrapper,使用直接 rustc。', + isPosixSccacheBypass + ? '[dev:rust] POSIX 本地 dev 构建绕过 sccache,使用直接 rustc。' + : rustcWrapper + ? '[dev:rust] 本地 dev 构建启用 sccache wrapper。' + : configuredWrapper + ? '[dev:rust] sccache wrapper 探测失败,回退到直接 rustc。' + : '[dev:rust] 未显式配置 Rust wrapper,使用直接 rustc。', ); } return mergedEnv; diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 35d2aec26..169c361f7 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -679,6 +679,32 @@ describe('dev scheduler Rust build env', () => { } }); + test('POSIX 显式配置 sccache 时日志说明实际绕过', () => { + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + 'platform', + ); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + Object.defineProperty(process, 'platform', { + configurable: true, + value: 'linux', + }); + try { + const env = buildLocalRustProcessEnv( + { RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'sccache' }, + { log: true }, + ); + expect(env.RUSTC_WRAPPER).toBe('/usr/bin/env'); + expect(warn).toHaveBeenCalledWith( + '[dev:rust] POSIX 本地 dev 构建绕过 sccache,使用直接 rustc。', + ); + } finally { + warn.mockRestore(); + if (originalPlatform) + Object.defineProperty(process, 'platform', originalPlatform); + } + }); + test('local dev Rust env enables available sccache wrapper', () => { const originalPlatform = Object.getOwnPropertyDescriptor( process, -- 2.52.0 From 13035d4d29eb2bcb4c4bdb92335c13ed7ce17698 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 08:52:01 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=E7=BC=93=E5=AD=98=20sccache=20=E6=8E=A2?= =?UTF-8?q?=E6=B5=8B=E7=BB=93=E6=9E=9C=E9=81=BF=E5=85=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在当前 dev 编排进程内复用 sccache wrapper 探测结果 避免 API 与 worker 重启重复同步等待 同步更新排障文档 --- docs/project-memory/shared-memory/pitfalls.md | 2 +- scripts/dev.mjs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index b3e3163bf..46c7d6d95 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1193,7 +1193,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 现象:Windows 上执行 `npm run dev:api-server` 时,api-server 在 Cargo 启动阶段失败,日志出现 `error: multiple input filenames provided (first two filenames are ... rustc.exe and -)`,`/healthz` 无法访问。 - 原因:`server-rs/.cargo/config.toml` 默认配置 `rustc-wrapper = "sccache"`;本地 dev 脚本为了绕过损坏的 sccache 需要覆盖 wrapper。Windows 下如果把 `RUSTC_WRAPPER` 设置为 `rustc`,Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,真实 rustc 把 wrapper 传入的 rustc 路径和 stdin `-` 都当输入文件。 -- 处理:Windows 本地 dev 脚本默认把两个 wrapper 设为空字符串;只有用户显式配置 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 时才处理 sccache。sccache 配置会先限时执行真实 `sccache rustc -vV` 探测,失败、超时或两个变量冲突时回退到直接 rustc;Linux 保持 `/usr/bin/env` 绕过 sccache。 +- 处理:Windows 本地 dev 脚本默认把两个 wrapper 设为空字符串;只有用户显式配置 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 时才处理 sccache。sccache 配置会在当前 dev 编排进程内只执行一次限时真实 `sccache rustc -vV` 探测,并缓存成功或失败结果;失败、超时或两个变量冲突时回退到直接 rustc,避免每次 API / worker 重启重复同步阻塞;Linux 保持 `/usr/bin/env` 绕过 sccache。 - 验证:`npm run test -- scripts/dev.test.ts -t "dev scheduler Rust build env"`;POSIX 显式配置 sccache 时日志应明确说明绕过并使用直接 rustc;再用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。 - 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 62d4f2732..ebedfea92 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -146,6 +146,8 @@ function applyLocalFfmpegEnv(env, platform = process.platform) { return env; } +const localSccacheProbeCache = new Map(); + function resolveLocalDevRustcWrapperBypass(options = {}) { if (process.platform !== 'win32') { return '/usr/bin/env'; @@ -154,6 +156,12 @@ function resolveLocalDevRustcWrapperBypass(options = {}) { const sccacheAvailable = options.sccacheAvailable ?? (() => { + const cacheKey = `${process.platform}:sccache`; + const cached = localSccacheProbeCache.get(cacheKey); + if (cached !== undefined) { + return cached; + } + const result = spawnSync('sccache', ['rustc', '-vV'], { cwd: repoRoot, encoding: 'utf8', @@ -161,7 +169,9 @@ function resolveLocalDevRustcWrapperBypass(options = {}) { windowsHide: true, timeout: options.sccacheProbeTimeoutMs ?? 10_000, }); - return !result.error && result.status === 0; + const available = !result.error && result.status === 0; + localSccacheProbeCache.set(cacheKey, available); + return available; })(); return sccacheAvailable ? 'sccache' : ''; } -- 2.52.0 From f7e48eba9e166b3aa2e8cfc448565b0551c45c3d Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 08:58:31 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=B7=AF=E5=BE=84=E6=8E=A2=E6=B5=8B=20sccach?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows wrapper 探测改用显式配置的可执行路径 将 wrapper 路径纳入探测缓存键 --- scripts/dev.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/dev.mjs b/scripts/dev.mjs index ebedfea92..bca97def5 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -148,7 +148,7 @@ function applyLocalFfmpegEnv(env, platform = process.platform) { const localSccacheProbeCache = new Map(); -function resolveLocalDevRustcWrapperBypass(options = {}) { +function resolveLocalDevRustcWrapperBypass(configuredWrapper, options = {}) { if (process.platform !== 'win32') { return '/usr/bin/env'; } @@ -156,13 +156,13 @@ function resolveLocalDevRustcWrapperBypass(options = {}) { const sccacheAvailable = options.sccacheAvailable ?? (() => { - const cacheKey = `${process.platform}:sccache`; + const cacheKey = `${process.platform}:${configuredWrapper}`; const cached = localSccacheProbeCache.get(cacheKey); if (cached !== undefined) { return cached; } - const result = spawnSync('sccache', ['rustc', '-vV'], { + const result = spawnSync(configuredWrapper, ['rustc', '-vV'], { cwd: repoRoot, encoding: 'utf8', shell: true, @@ -628,7 +628,7 @@ function buildLocalRustProcessEnv(env, options = {}) { } const rustcWrapper = configuredWrapper - ? resolveLocalDevRustcWrapperBypass(options) + ? resolveLocalDevRustcWrapperBypass(configuredWrapper, options) : ''; mergedEnv.RUSTC_WRAPPER = rustcWrapper; mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapper; -- 2.52.0 From 0d8d3bd32078303a7b28246f5355ef550f8f41e8 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 08:59:29 +0000 Subject: [PATCH 8/9] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20AGC=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同步 AI Game Creator Shell 的包、Tauri 和锁文件版本 --- apps/ai-game-creator-shell/package.json | 2 +- apps/ai-game-creator-shell/src-tauri/Cargo.lock | 2 +- apps/ai-game-creator-shell/src-tauri/Cargo.toml | 2 +- apps/ai-game-creator-shell/src-tauri/tauri.conf.json | 2 +- package-lock.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index da7eddb68..c08cb6ac9 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -1,7 +1,7 @@ { "name": "@genarrative/ai-game-creator-shell", "private": true, - "version": "0.1.27", + "version": "0.1.29", "type": "module", "scripts": { "dev": "node scripts/start-tauri-dev.mjs", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.lock b/apps/ai-game-creator-shell/src-tauri/Cargo.lock index 60032820b..7e07a3c9c 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.lock +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.lock @@ -1725,7 +1725,7 @@ dependencies = [ [[package]] name = "genarrative-ai-game-creator-shell" -version = "0.1.27" +version = "0.1.29" dependencies = [ "agent-runtime-core", "axum", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.toml b/apps/ai-game-creator-shell/src-tauri/Cargo.toml index 370777cf3..4fe37d25a 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.toml +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "genarrative-ai-game-creator-shell" -version = "0.1.27" +version = "0.1.29" edition = "2021" publish = false diff --git a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json index 8cf521851..7fc0d7cf1 100644 --- a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json +++ b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Genarrative AI Game Creator", - "version": "0.1.27", + "version": "0.1.29", "identifier": "world.genarrative.ai-game-creator", "build": { "beforeDevCommand": "npm --prefix ../.. run agc:serve", diff --git a/package-lock.json b/package-lock.json index 0296fa010..98d451a05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,7 +95,7 @@ }, "apps/ai-game-creator-shell": { "name": "@genarrative/ai-game-creator-shell", - "version": "0.1.27", + "version": "0.1.29", "dependencies": { "@cubone/react-file-manager": "^1.35.0", "@genarrative/image-canvas-core": "0.1.0", -- 2.52.0 From 1b2a41994b35582ae7ebe12d1eb6a0df8b695b44 Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 14 Sep 2026 09:04:49 +0000 Subject: [PATCH 9/9] =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=98=BE=E5=BC=8F=20scca?= =?UTF-8?q?che=20wrapper=20=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 探测成功后将用户配置的 wrapper 原样交给 Cargo --- scripts/dev.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev.mjs b/scripts/dev.mjs index bca97def5..aae03d3b2 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -173,7 +173,7 @@ function resolveLocalDevRustcWrapperBypass(configuredWrapper, options = {}) { localSccacheProbeCache.set(cacheKey, available); return available; })(); - return sccacheAvailable ? 'sccache' : ''; + return sccacheAvailable ? configuredWrapper : ''; } const SERVICE_NAMES = [ -- 2.52.0