15、编译错误排查:常见编译错误、日志分析、Kati 错误定位
说实话,写 Android 系统最怕什么?不是需求改来改去,而是编译不过。
我见过太多新人,一看到终端里飘红就慌了,直接截图往群里丢。其实编译错误没那么可怕,关键是你得知道怎么看日志、怎么定位问题。今天我就把这几年的排查经验掰开揉碎讲给你听。
15.1 编译错误的“三大家族”
Android 编译错误,说白了就三类:语法错误、链接错误、还有 Kati 相关的构建错误。我习惯把它们分成“代码问题”、“依赖问题”和“构建系统问题”。
- 语法错误:C++/Java/Kotlin 代码写错了,编译器直接报错。这种最好修,看行号就行。
- 链接错误:函数声明了没实现,或者库没链接对。我遇到过最坑的一次,是 .bp 文件里少写了一个 shared_libs。
- Kati 错误:这是 Android 特有的。Kati 负责把 Android.mk 转换成 ninja 文件,如果语法写错或者变量引用不对,就会挂。
核心原则:先看第一个错误,不要被后面几十个刷屏吓到。很多时候后面的错误都是第一个引发的连锁反应。
15.2 日志分析:你得知道看哪里
编译日志通常很长,但真正有用的信息就那么几块。我个人习惯用 grep 或者 less 来过滤。
15.2.1 关键关键词
error:—— 真正的错误FAILED:—— ninja 构建失败的任务***—— make 时代的错误标记,现在 Kati 也会用undefined reference—— 链接错误,常见于 C++
举个例子,你看到这样的输出:
FAILED: out/target/product/xxx/obj/SHARED_LIBRARIES/libfoo_intermediates/LINKED/libfoo.so
/bin/ld: error: undefined symbol: bar_func
嗯,这里就说明 libfoo.so 里调用了 bar_func 但没找到实现。要么是没链接对应的库,要么是那个库没编译。
15.2.2 日志文件在哪
编译日志默认在 out/soong.log 和 out/build.log。我建议你养成习惯,每次编译失败后先看 build.log 的最后 200 行:
tail -200 out/build.log
为什么?因为真正的错误往往在最后面。前面那些 warning 和 info 大多是噪音。
小技巧:用 ninja -v 重新跑失败的任务,能看到完整的命令行和错误输出。我经常这么干。
15.3 Kati 错误定位:别被“伪错误”骗了
Kati 是 Android 构建系统里一个比较特殊的环节。它把 Android.mk 转换成 ninja 文件。如果 Kati 报错,通常是因为 .mk 文件写错了。
15.3.1 常见 Kati 错误类型
| 错误信息 | 含义 | 常见原因 |
|---|---|---|
*** missing separator. |
Makefile 语法错误 | 该用 Tab 的地方用了空格 |
*** commands commence before first target. |
命令出现在目标之前 | 空行或缩进问题 |
*** unterminated variable reference. |
变量引用没闭合 | 少写了 ) 或 } |
kati: unknown option |
Kati 参数错误 | Android.mk 里用了不支持的语法 |
我曾经遇到过一个案例:同事在 Android.mk 里写了一个 $(shell ...) 调用,里面用了 bash 的 <( ... ) 进程替换。Kati 不认这个,直接报错。折腾了半天才发现是 shell 语法太“高级”了。
15.3.2 如何定位 Kati 错误
Kati 的错误信息通常包含文件名和行号。比如:
build/make/core/base_rules.mk:238: *** missing separator. Stop.
这时候直接打开 base_rules.mk 的第 238 行附近看看。我敢打赌,八成是有人把 Tab 换成了空格。
注意:Kati 错误有时候会指向 build/make/core/ 下的通用文件,但问题其实出在你的模块里。别被误导了。检查一下你的 Android.mk 里有没有不规范的写法。
15.4 实战:一个典型的排查流程
假设你执行 m 编译,终端输出一片红。别慌,按我的步骤来:
- 看第一个 FAILED:找到第一个
FAILED:行,记住它后面的目标名。 - 看错误类型:是编译错误还是链接错误?如果是编译错误,直接去源码对应行修。
- 如果是链接错误:检查
Android.bp或Android.mk里的shared_libs、static_libs有没有漏掉。 - 如果是 Kati 错误:打开报错文件,看行号附近。注意 Tab 和空格。
- 重新编译:修完后,用
m 目标名单独编译那个模块,验证通过后再全编。
我记得有一次,一个同事编译整整挂了两个小时,最后发现是 Android.mk 里多了一个不可见字符。嗯,从那以后我养成了用 cat -A 检查 .mk 文件的习惯。
15.5 知识体系:一张图看懂编译错误排查
下面这张图是我自己总结的排查路径,你可以把它当成一个决策树来看:
这张图从左到右,从上到下,基本覆盖了 90% 的编译错误场景。你只要跟着走一遍,大部分问题都能自己搞定。
15.6 避坑指南
最后分享几个我踩过的坑:
- 不要相信“clean 一下就好了”:很多时候 clean 只是浪费时间。先看日志,找到根因。
- 注意大小写:Android 构建系统对文件名大小写敏感。我见过有人把
foo.cpp写成Foo.cpp,结果链接报错。 - Kati 缓存问题:有时候 Kati 会缓存旧的 .mk 解析结果。如果你改了 .mk 但没生效,试试
touch Android.mk或者rm -rf out/build_kati。 - 并行编译的坑:用
m -j32时,错误信息可能会乱序。我建议第一次编译用m -j1,虽然慢,但日志清晰。
嗯,编译错误排查其实就是一个“读日志 → 定位 → 修复”的循环。你多练几次,自然就熟练了。别怕,我也是一路从“看到红色就慌”走到现在的。