资讯动态

Android编译报错FAILED: ninja: ‘==xxx_intermediates‘ 的定位与修复

发布时间:2026/10/6 4:58:28 来源:尧图企业网站定制
今天在服务商配的编译机上整了一下午 Android 定制系统全量编译走到一半突然炸了这么一条FAILED: ninja: out_sys/target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/ ninja: build stopped: subcommand failed.第一眼看到这个报错我后背就一凉。不是因为它有多难而是因为 platform-lib-local 这种字符串出现在构建路径里十有八九是源码里面哪个模块名或者 PRODUCT_PACKAGES 引用写脏了。这种问题不像编译器报语法错误那么直白光看报错根本不知道是哪个文件、哪一行引出来的得顺着构建系统手动去翻。 这篇文章就把我这次排查的思路、定位命令、修复方式和后续的预防手段完整写出来。如果你也在编译 AOSP 或者其他基于 Soong/Ninja 的 Android 源码时遇到 FAILED: ninja: xxx_intermediates/ 这种诡异路径问题可以直接照着一步步来能省下不少折腾的时间。 ## 1. 报错现场与第一层解读 ### 1.1 完整的报错形式和编译环境 先把现场还原一下。我当时用的构建配置是 lunch xxx-userdebug输出目录被指定为 out_sys通过 OUT_DIRout_sys 指定的Android 源码版本是 Android 12 分支下的定制方案。执行 m -j32 之后编译进程跑了大概十几分钟突然在链接阶段报错退出。 终端里最后几行的完整输出大致是 bash [ 5% 684/12345] //frameworks/base/services/core/java:services.core.xml minimal xml rules FAILED: ninja: out_sys/target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/ ninja: build stopped: subcommand failed.注意看这行报错的结构FAILED: ninja: ...其实是 Ninja 在告诉我们整个构建图里存在一个目标out_sys/target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/但是整个构建图中没有任何一条 rule 能够生成这个目标。这就好比你给一个文件写了一行依赖关系但写了依赖却不告诉构建系统这文件怎么来Ninja 自然直接罢工。这里的out_sys/target/common/obj/JAVA_LIBRARIES/是 Java 类库的公共输出目录属于 Android 构建系统里固定的产物路径模板。末尾的_intermediates是 Android 构建系统为模块生成中间产物时自动拼接的后缀。也就是说这个目标本质上是一个名为platform-lib-local的 JAVA 库模块的中间输出目录。1.2 出现在构建路径中意味着什么熟悉 Android 模块命名规范的开发者都知道模块名在 Soong/Kati 体系里只允许使用字母、数字、下划线、点号和中划线这类常规字符。这个字符在 Makefile 里有赋值语义在 Android.bp 里有键值对分隔语义正常情况下永远不会作为模块名的一部分传到构建系统里。当路径中出现两个连续的几乎可以断定是某个代码文件在拼接字符串时把原样写进了模块名或者依赖引用里。常见的情况包括Android.mk里写LOCAL_MODULE : platform-lib-local。Android.bp里的name: platform-lib-local。PRODUCT_PACKAGES里追加了一个字符串platform-lib-local。某处用$(if)或$(eval)拼接时多带了一个。这些场景的共性都是代码作者本意可能只是写判断逻辑、写注释、或者临时改一个模块名验证什么功能结果把这个在命令行或脚本里看起来无害的字符串传给了构建系统。所以看到这种报错第一反应不应该是清空整个out_sys目录重新全量编那不仅慢而且大概率解决不了问题。正确的思路是顺着模块名去源码里定位这个platform-lib-local到底在哪里被引用、被定义。2. 根因排查把从源码中揪出来2.1 在整个源码树中搜索可疑模块名排查这种字符串问题第一板斧就是全源码 grep。我建议分两步走第一步先搜完整的脏字符串第二步再搜去掉后的正常模块名用来对比确认有没有同名文件。# 搜索包含 platform-lib-local 的文件 grep -rn platform-lib-local --include*.mk --include*.bp --include*.soong . # 搜索不含 的模块名引用 grep -rn platform-lib-local --include*.mk --include*.bp device/ vendor/ build/ 2/dev/null我这次搜第一遍就锁定了问题。结果出现在vendor/xxx/device/xxx.mk文件里内容大致是这样PRODUCT_PACKAGES platform-lib-local这行代码出现在某个PRODUCT_PACKAGES集合里本意应该是想加入模块platform-lib-local但在复制或者用脚本批量生成 product 配置的时候前面多带了一个。于是字符串platform-lib-local变成了一个模块名被 Soong 当成了一个真实存在的模块依赖。如果你第一步没有搜到全字符串可以放宽条件只搜platform-lib-local然后用肉眼逐条审看有没有哪一行的模块名前后被拼了特殊字符。尤其是那种用模板拼接的 mk 文件例如PRODUCT_PACKAGES $(foreach lib,$(LOCAL_LIBS),$(lib))这种$(lib)的写法就会在循环展开后产生platform-lib-local这样的脏模块名。2.2 顺着 Android.bp / Android.mk 追踪定义与引用搜到PRODUCT_PACKAGES platform-lib-local之后还不能立刻下结论因为也有可能真正问题出在这个模块的定义文件里。所以下一步要看这个模块到底存不存在以及定义它的文件里name字段有没有被写脏。在 AOSP 体系里模块定义一般集中在packages/、frameworks/、vendor/、device/这几个大目录下搜索模块名platform-lib-local时可以加一层过滤只搜索定义和引用最密集的类型grep -rn platform-lib-local --include*.bp --include*.mk \ packages/ frameworks/ vendor/ device/ hardware/ 2/dev/null我当时搜索后发现全源码里并没有一个合法的名为platform-lib-local的模块定义只有PRODUCT_PACKAGES里那一个脏引用。这就更加确定问题了引用的是个不存在的模块名且模块名被拼上了。为了进一步确认构建系统是否还有其他地方也在引用这个字符串还可以查产物输出目录里有没有实际生成过相关的中间文件find out_sys -type d -name *platform-lib-local* | head -20如果有目录说明之前在某个阶段 Soong 曾尝试处理这个模块名或者构建图中残留过节点如果没有就是单纯的构建图引用错误。我这次查下来的结果是没有任何实际目录进一步坐实了它只是字符串污染。2.3 找出的真正源头条件赋值与脚本拼接很多时候问题并不像上文那么简单直接。我曾经遇到过另一种情况源码里搜不到platform-lib-local这种完整字符串但 Ninja 报错里依然出现了类似的目标路径。这种情况的元凶通常是条件赋值或者函数宏拼接让字符在预处理之后才变成xxx。举个例子某个 mk 文件里写了PRODUCT_PACKAGES $(if $(filter eng userdebug,$(TARGET_BUILD_VARIANT)),build-tools,)这种写法的结果是正常的build-tools字符串。但如果写成下面这样就会出问题PRODUCT_PACKAGES $(if $(filter eng userdebug,$(TARGET_BUILD_VARIANT)),build-tools,)注意第二个参数build-tools如果前面的条件判断满足那么展开后的结果就是build-tools从而把build-tools当成一个模块名传给构建系统。同理如果哪个文件里有$(eval LOCAL_MODULE : $(some_var))且some_var里面拼了几个等号最终也会造成类似的脏路径。所以排查时如果第一板斧搜不到完整字符串第二步一定要盯住所有$(if ...)、$(eval ...)、$(foreach ...)以及:赋值右侧以开头的写法。这些地方最容易产生肉眼搜不到的脏字符串。我当时还顺手用了另一个比较笨但有效的招把整个源码目录里所有PRODUCT_PACKAGES相关的行捞出来逐个看有没有以开头追加的项。grep -rn PRODUCT_PACKAGES --include*.mk . | grep 这样能一次性看到所有可能掺入的 PRODUCT_PACKAGES 引用。3. 编译系统视角Soong/Ninja 如何生成错误节点3.1 模块名到输出路径的映射规则要彻底理解这个报错需要知道 Android 构建系统从源码到 Ninja 构建图的链路。Android 7.0 之后构建系统由 KatiMake 解析和 SoongBlueprint / Android.bp 解析共同组成。Kati 负责把 Android.mk 转换成 Ninja 文件Soong 负责把 Android.bp 转换成 Ninja 文件最后由 Ninja 统一执行构建。当一个模块被定义成一个 Java 库例如java_library { name: platform-lib-local, srcs: [src/**/*.java], }那么它默认的输出目录就是out_sys/target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/其中JAVA_LIBRARIES是模块的类型目录platform-lib-local是模块名_intermediates是固定后缀。整个路径由 Soong 在生成构建图时自动拼接。当模块名里出现时路径就变成了platform-lib-local_intermediates。从文件系统的角度讲并不是非法字符Linux 下创建一个名为platform-lib-local_intermediates的目录是允许的。问题在于 Ninja 的构建图规则里必须有某个 rule 指明如何生成这个路径下的文件否则这个目标就是一个无源目标Ninja 无法执行。3.2 Ninja 报错FAILED: ninja的实际含义Ninja 报错FAILED: ninja: some/path并不是说某个命令执行失败了而是说在整个构建图中这个目标被某些节点引用为依赖但构建图中根本不存在能生成它的 rule。好比你在地图上标了一个目的地但没画任何一条能通往目的地的路。导航软件自然只能提示无法规划路径。在 Android 构建的语境中通常是谁引用了这个目标呢最常见的是PRODUCT_PACKAGES里的模块名。当 Kati 或 Soong 处理到PRODUCT_PACKAGES platform-lib-local这行时构建系统会认为产品需要打包一个名为platform-lib-local的模块。于是它会在构建图里把这个模块对应的产物文件挂在依赖树上等到 Ninja 执行时发现没有任何 rule 可以生成这个文件于是整条构建中断。如果你遇到的是更复杂的情况不想靠猜可以直接用 Ninja 自带的查询命令看依赖关系。3.3 用 ninja -t 查询命令定位依赖来源Ninja 提供了-t参数可以查看目标之间的依赖关系。我们可以在out_sys目录下执行查询看看这个诡异的节点到底被谁引用、有哪些依赖。注意如果路径里含有在 shell 里要用引号包住整个路径防止被解释成特殊参数。cd out_sys ninja -t query target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/这个命令会输出目标的上游依赖和下游依赖如下游依赖是某个image或者system.img、vendor.img之类的打包目标就说明有PRODUCT_PACKAGES在把它往系统镜像里塞。如果-t query输出的信息不够直观还可以用-t targets配合 grep 过滤出构建图中所有包含该关键字的节点cd out_sys ninja -t targets | grep platform-lib-local | head -20我这次查到的结果是构建图中出现了若干指向out_sys/target/product/xxx/system/framework/platform-lib-local.jar的节点这些节点全部都由PRODUCT_PACKAGES里的脏模块名引出来的。锁定这一步修复就水到渠成了。4. 修复步骤与清理技巧4.1 修改脏引用或模块定义根据上一步锁定的文件位置修复其实很简单。我这次只需要把vendor/xxx/device/xxx.mk里的这一行PRODUCT_PACKAGES platform-lib-local修改成PRODUCT_PACKAGES platform-lib-local这里要注意一个重要问题改完引用后如果项目中确实存在名为platform-lib-local的模块但同时它的name字段也被写成了platform-lib-local那么还需要去模块定义文件里同步改回来。例如Android.bp文件里有java_library { name: platform-lib-local, srcs: [src/**/*.java], }就要改成java_library { name: platform-lib-local, srcs: [src/**/*.java], }同时检查所有依赖它的static_libs、libs、PRODUCT_PACKAGES等字段有没有同步。如果模块名本身合法但只是引用处脏了那只需要改引用处。修改之后不需要清理整个输出目录直接重新执行 make构建系统会重新生成 Ninja 构建图。因为你改动的文件属于构建配置类文件Kati/Soong 会检测到依赖变化自动触发重跑。4.2 清理残留的异常中间目录虽然改完配置后正常编译不会再生成platform-lib-local_intermediates这个目录但如果之前已经有部分脏产物落盘最好还是手动清一下避免后续出现奇怪的文件残留。清理命令rm -rf out_sys/target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/如果源码里还有路径名包含的其他残留也可以一次性把所有类似目录都找出来删掉# 先扫一遍看看有哪些 find out_sys -type d -name *_intermediates | head -50 # 确认没问题后批量删除 find out_sys -type d -name *_intermediates -exec rm -rf {} 这里我不建议没确认就直接执行批量删除。先扫一遍、肉眼看清楚里面是什么再动刀不然误删了某些正常模块的中间产物会让后续编译变慢。4.3 重新触发编译的正确姿势修复完成后重新编译建议分三步走source build/envsetup.sh lunch 你的产品名-userdebug m -j32如果上一步的修复涉及PRODUCT_PACKAGES这种全局变量而且你担心旧的 Ninja 构建图没有被自动更新可以强制删除生成规则文件让 Soong/Kati 重新生成rm -rf out_sys/build-*.ninja rm -rf out_sys/soong rm -rf out_sys/.module_paths然后再执行m -j32。这一步会强制让构建系统重新解析所有Android.bp和Android.mk基本上可以杜绝改完还是报同样错的尴尬。需要注意直接删除out_sys/soong会重新生成大量模块描述文件编译前期会多花几分钟。相比全量删除out_sys来说已经非常温柔了至少不需要重新跑完整套 C/C 编译。5. 同类问题的预防与快速排查清单5.1 模块命名规范与强制检查清单这次问题虽然定位快但本质上也是低级命名规范问题。为了不反复踩坑我在项目里制定了一个简单的检查清单每次提交代码前或者出问题时先跑一遍。下面的命令可以直接复制成脚本放在 CI 或者本地 pre-commit 钩子里#!/bin/bash # 检查 PRODUCT_PACKAGES / PRODUCT_PACKAGES_DEBUG 中是否有以 开头的脏模块名 grep -rn PRODUCT_PACKAGES.* --include*.mk device/ vendor/ build/ 2/dev/null # 检查 Android.bp 中 name 字段是否以 开头 grep -rn name: --include*.bp packages/ frameworks/ vendor/ device/ hardware/ 2/dev/null # 检查 Android.mk 中 LOCAL_MODULE 是否以 开头 grep -rn LOCAL_MODULE.* : * --include*.mk packages/ frameworks/ vendor/ device/ hardware/ 2/dev/null如果上面三条命令都没有输出说明模块命名层面基本健康。另外建议在每一个新模块引入构建系统时遵守只使用[A-Za-z0-9_.-]的约定。、//、..这类字符串一旦混进模块名轻则像这次一样 Ninja 报错重则可能引起路径穿越或者覆盖其他模块的产物排错成本远大于命名时多看一眼的成本。5.2 在 CI 中提前拦截如果团队里很多人都在改 vendor 和 device 下的 product 配置纯靠自觉不太现实。我比较推荐在 CI 的编译前检查阶段加上上面那段脚本。CI 一旦发现PRODUCT_PACKAGES里有以开头的字符串直接让流水线失败并输出错误文件路径。这样能在编译还没开始之前就把问题挡在门外。对于已经发现的问题修复后建议顺手做一次脏配置全扫描把所有可能的不规范字符都找出来而不是只改当前报错的这一个。我这次修复完就扫描了一遍结果还真在vendor/xxx/common/xxx.mk里又找到一个类似的lib-local字符串尽管它这次还没引爆 Ninja 报错但留着就是个定时炸弹。5.3 实用 debug 命令速查最后把我这次排错用到的命令整理成一个速查表遇到 Ninja 报错时按顺序执行基本都能快速定位到是哪个文件在搞事。目的命令搜源码里完整的脏字符串grep -rn platform-lib-local --include*.mk --include*.bp .搜去脏后的模块名引用grep -rn platform-lib-local --include*.mk --include*.bp vendor/ device/ build/看构建图中目标列表cd out_sys ninja -t targets | grep platform-lib-local查看目标依赖关系cd out_sys ninja -t query target/common/obj/JAVA_LIBRARIES/platform-lib-local_intermediates/清理残留目录find out_sys -type d -name *_intermediates -exec rm -rf {} 强制重建构建图rm -rf out_sys/build-*.ninja out_sys/soong out_sys/.module_paths这些命令不仅适用于JAVA_LIBRARIES也适用于APPS、STATIC_LIBRARIES、SHARED_LIBRARIES等所有模块类型。只要报错路径里出现特殊字符先不要急着清理全量输出按表格里的顺序查一遍通常十几分钟内就能定位到脏字符串所在的文件。我在实际遇到这类 Ninja 报错时最反感的做法就是一上来就rm -rf out_sys。全量编译一次动辄两三个小时如果根因是某个 mk 文件里多写了一个清理全量输出毫无意义。先花几分钟用 grep 和ninja -t query定位到具体文件改完配置再重编才是最省时间的路径。希望这次踩坑记录能帮你在下次看到FAILED: ninja: ...intermediates/时少走一点弯路直接按住这个思路把脏字符串揪出来改掉。如果你在 Android 源码编译时还遇到过其他奇奇怪怪的模块名报错也可以按这个思路试试看。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑