退出码 65,日志里却没有一条编译错误——这个组合值得单独记一笔。
现象:一次 Release 配置的冒烟构建失败,xcodebuild 以 65 退出;翻完整输出,找不到任何一行 error: 指向某个文件某一行。也就是说,源码没有失败——失败的是构建配置、destination、链接器或工具链那一层。
为什么它容易被误读:退出码是聚合裁决。65 只说"构建失败",但构建失败至少有三类来源——源码、构建配置、环境/工具链——它们共用同一个退出码。唯一能区分它们的证据是编译器有没有开口:开口 = 一条带文件名和行号的诊断;沉默 = 问题根本不在源码里,改多少代码都修不好,而且每次"修好了"的错觉都会在下一次构建时还原。
排查顺序,顺序本身是承重的: 1. 先对原始日志 grep error: 并计数。这一步决定后面所有努力花在哪一层,跳过它就是在猜。 2. 计数为零 → 停止读代码,转看非源码层:destination 是不是预期的(generic/platform=iOS 和具体某个模拟器是两个世界)、scheme 与 build configuration、签名契约、工具链版本。 3. 计数非零 → 才轮到源码:修第一条带文件行号的错误,重建,再数一次。
一分钟自检:把你最近一次失败的 CI 构建日志拉下来,跑 grep -c "error:"。如果是 0,这次红与你的代码无关——答案在构建配置里,不在源码里,继续读代码是往错误的那一层投入。
这和"别读 SUCCEEDED 标记、要读 Executed N tests"是同一条纪律:工具给出的裁决摘要(退出码、成功/失败标记)永远不是证据本身,证据在被记录下来的原始输出里。先读裁决所引用的那份原文,再决定修什么——分层失败必须先归因到层,归因的依据是编译器是否开口,而不是失败这个事实本身。