支持 Windows 本地 sccache 自动回退 #351

Closed
lhk229 wants to merge 3 commits from opt/win-ssache into master
3 changed files with 48 additions and 18 deletions
@@ -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 <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/<pid>/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 构建缓存与磁盘上限
+21 -5
View File
@@ -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;
+26 -12
View File
@@ -657,18 +657,32 @@ describe('dev scheduler local worker cleanup', () => {
});
describe('dev scheduler Rust build env', () => {
test('local dev Rust env bypasses project sccache wrapper', () => {
const env = buildLocalRustProcessEnv(
{
RUSTC_WRAPPER: '/usr/bin/sccache',
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
},
{ log: false },
test('local dev Rust env enables available sccache wrapper', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform',
);
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32',
});
expect(env.RUSTC_WRAPPER).not.toBe('/usr/bin/sccache');
expect(env.RUSTC_WRAPPER).not.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', () => {
@@ -684,7 +698,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 +714,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('');