环境:Windows + 国内网络,Flutter 3.44.x / Gradle 8.10.2。上篇讲锁文件,这篇讲一个更隐蔽的问题:构建进程看着活着,其实早就死了。
构建脚本后台运行,日志文件持续增长,心跳输出一切正常——你以为它在干活,实际上它已经僵死了一个半小时。这时候你的选择只有两个:继续等(大概率继续白等),或者杀掉重来(之前的时间全沉没)。
区分这两种状态,是构建排障里最值钱的技能。
以我排查实锤的案例还原一下:
bin/cache/lockfile 残留注意第 2 步:"只警告不阻塞"的设计是僵死的帮凶。删锁失败必须当致命错误处理,立即退出并给出手动处理指引。
① 进程链完整:构建正常推进时,dart.exe 必然存在(AOT 编译阶段)。用:
tasklist | grep -iE "java|dart|gradle"Gradle 的 java 进程在而 dart.exe 迟迟不出现 = 大概率卡在锁上。
② 产物目录有文件活动:
find <项目>/build -newermt "10 min ago" -type f | head10 分钟内 build 目录零新文件,基本可以判死。
③ 任务阶段在推进:日志里的 Gradle task 名(:app:compileXXX)应该不断往前走。停在同一个 task 超过 30 分钟且产物无活动 = 僵死。
经验阈值:30 分钟 build 目录零文件活动,立即处理,不要抱侥幸心理。
另一个容易误判的点:全量构建到底该多久?
我实测的数据:正常全量构建(clean build)15 分钟左右。但有一种情况会到 40-70 分钟也属正常——全新的 GRADLE_USER_HOME 首轮构建,配置阶段要做几千个依赖的 checksum 校验和远程 HEAD 探测,光这就能花掉 20 多分钟。
区分方法还是上面三条:慢 = 日志持续推进 + task 名不断变化 + 最终成功;死 = 停在同一个地方 + 产物零活动。日志在动就是真干活,别急着杀。
常规思路是构建前把旧的缓存/构建目录 rm -rf 掉。但如前文所说,旧文件的删除在 Windows 上经常被安全机制拦截(删新文件没事,删旧文件被拦)。怎么办?
用 rename 代替 delete:
// 把旧目录改名"泊车",让本轮构建在全新路径上跑
fs.renameSync(oldDir, oldDir + "_stale_" + Date.now());改名(rename)操作不走删除通道,基本不会被拦。构建永远在全新目录上进行,旧目录留着事后统一清理。
这个策略还有个附加收益:每轮都是 clean build,产物干净可复现。代价是没有增量编译、每轮全量 AOT 编译——但在文件写入随时可能被拦的环境里,全量重建反而是唯一稳定解。
每轮泊车都留一个 *_stale_* 目录,脚本如果从不清理,用不了多久就是几百个目录、几个 GB 的垃圾。实测一个项目一个月攒了 278 个 stale 目录 + 4.7GB build 垃圾。
泊车策略必须配套清理机制:每轮构建前先扫一遍 *_stale_* / *_bak_*,超过 N 天或总量超限就批量清掉。
两篇加起来就是一套完整的"构建环境止血方案"。第三篇讲讲这些坑是怎么被系统性沉淀下来的——工具链之外的工程方法论。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。