编译错误排查
AOSP 编译失败时,终端最后一行通常只说明“整个构建没有完成”,不一定指出真正的根因。并行构建还会 让多个 action 的输出交错:最早失败的 action、最后打印的 ninja failed 和源码中真正需要修改的位置, 可能是三件不同的事。
本文默认读者已经完成 lunch目标与编译变体,并了解 m-mm-mmm编译命令、Ninja构建系统 与 模块化编译。这里不展开每种语言的全部诊断,而是建立一条可重复的排查 主线:先保存失败信息,再确定失败阶段和 owner,最后用最小目标复现、修复并回归验证。
1. 构建流程
1.1 构建路径
常见概括是“Soong → Kati → Ninja”,它有助于记忆,却不足以定位 Android 17 的真实失败边界。 soong_ui 还要完成主机预检、产品配置、环境变化检查、构建图合并和执行器选择。
build/soong/ui/build/build.go 中的 Build 按配置决定哪些阶段需要运行:
源码文件:build/soong/ui/build/build.go
相关函数/类型:Build
checkProblematicFiles(ctx)
checkRAM(ctx, config)
SetupOutDir(ctx, config)
SetupTempDir(ctx, config)
checkCaseSensitivity(ctx, config)
if what&RunProductConfig != 0 {
runMakeProductConfig(ctx, config)
}
if what&RunSoong != 0 {
runSoong(ctx, config, what&RunBuildTests != 0)
}
if what&RunKati != 0 {
runKatiCleanSpec(ctx, config)
runKatiBuild(ctx, config)
runKatiPackage(ctx, config)
}
// 说明:Soong和Kati的图先合并,再由Ninja或其他executor执行。
createCombinedBuildNinjaFile(ctx, config)
if what&RunNinja != 0 {
runNinjaForBuild(ctx, config)
}这里的 owner 是 soong_ui:它拥有本次构建配置并编排各阶段。Soong 与 Kati 的主要产物不是最终 APK 或镜像,而是供后续执行的 Ninja 规则;Ninja、Siso 等 executor 才消费这些规则并运行编译器、 链接器、代码生成器和打包工具。
因此,错误分类至少包括:
| 边界 | owner 或执行者 | 常见证据 |
|---|---|---|
| 主机与输出目录预检 | soong_ui | case sensitivity、磁盘、目录、并发锁错误 |
| 产品配置 | Make 产品配置入口 | product、release、variant、BoardConfig 路径和变量错误 |
| Soong/Blueprint | soong_build | Android.bp:行:列、属性、模块图、variant、visibility 错误 |
| Kati | ckati | .mk:行、***、重复 target、隐式规则、变量求值错误 |
| Ninja action | Ninja/Siso 与具体工具 | FAILED:、输出路径、完整 command、tool output、exit code |
| 构建后处理 | API 更新、打包或 dist 动作 | 对应 action 的命令和输出,而不是笼统的“Ninja 错误” |
1.2 combined图
combined 文件会载入 Kati 和 Soong 生成的子图:
源码文件:build/soong/ui/build/build.go
相关函数/类型:combinedBuildNinjaTemplate
var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
{{if and (not .SkipKatiNinja) .HasKatiSuffix}}
subninja {{.KatiBuildNinjaFile}}
subninja {{.KatiPackageNinjaFile}}
{{end -}}
subninja {{.SoongNinjaFile}}
`))所以“失败目标来自 Android.mk”不表示执行阶段不是 Ninja;“终端出现 FAILED:”也不表示错误一定来自 clang。必须查看失败 action 的命令,确认实际运行的是 ckati、soong_build、编译器、链接器还是 其他工具。
2. 失败定位
2.1 clean适用边界
刚失败时,out 目录里保留了最有价值的信息:生成图、失败命令、响应文件、depfile、日志和已经成功的 中间产物。立刻执行全量 clean 会同时删除日志和可复用结果,却不一定触及源码或配置根因。
先记录:
get_build_var TARGET_PRODUCT
get_build_var TARGET_BUILD_VARIANT
get_build_var OUT_DIR
git -C <changed-project> status --short若源码由多个 project 组成,还要记录相关 project 的 commit。选择性同步源码时,应同时确认报错依赖 对应的 project 是否确实存在;“模块未定义”既可能是依赖名写错,也可能是当前 manifest 根本没有检出 提供者。
2.2 三个核心日志
build/make/Usage.txt 定义了构建产生的常用日志:
| 文件 | 主要内容 | 适合回答的问题 |
|---|---|---|
$OUT_DIR/error.log | 失败 action 及其输出 | 哪个 action 失败,命令和工具原始输出是什么 |
$OUT_DIR/verbose.log.gz | 每个执行命令及输出 | 失败前后实际执行了什么,Kati regen 等详细信息是什么 |
$OUT_DIR/soong.log | soong_ui 调试信息 | 阶段、环境变化、critical path 和编排状态是什么 |
$OUT_DIR/build.trace.gz | 构建阶段与 action 时间线 | 失败发生在哪一段,是否伴随长时间阻塞或资源问题 |
日志可能轮转为带数字的旧版本。复现后应对照时间戳,避免把上一次构建的 error.log 当成本次证据。
2.3 error日志
失败 action 由状态层统一写入:
源码文件:build/soong/ui/status/log.go
相关函数/类型:errorLog.FinishAction
func (e *errorLog) FinishAction(result ActionResult, counts Counts) {
if result.Error == nil {
return
}
fmt.Fprintf(e.w, "FAILED: %s\n", result.Description)
if len(result.Outputs) > 0 {
fmt.Fprintf(e.w, "Outputs: %s\n", strings.Join(result.Outputs, " "))
}
fmt.Fprintf(e.w, "Error: %s\n", result.Error)
if result.Command != "" {
// 说明:完整命令比终端末尾的总失败信息更接近根因。
fmt.Fprintf(e.w, "Command: %s\n", result.Command)
}
fmt.Fprintf(e.w, "Output:\n%s\n", result.Output)
}Description 是人类可读的 action 描述,Outputs 是构建图中的输出节点,Command 是实际工具命令, Output 才是 clang、rustc、javac、linker 或脚本打印的诊断。排查时应把这四部分放在一起,而不是 只搜索所有出现 error: 的行。
状态 reader 在 action 非零退出时保存 exit code 和原始输出;如果 Ninja 异常退出,仍在运行的 action 会被记录为 cancelled。因此 action cancelled when ninja exited 往往是后果:应继续查找此前使执行器 退出的失败 action、信号或主机资源事件。
3. 失败分层
3.1 失败边界
不要在每次尝试时更换 product、variant、目标和并行度,否则无法判断变化来自修复还是实验条件变化。 推荐顺序:
# 1. 在相同lunch环境复现原目标
m <target>
# 2. 如果输出交错严重或怀疑内存压力,降低并行度
m -j1 <target>
# 3. 需要收集多个互不依赖失败时,有限度地继续
m -k2 <target>-j1 的作用是降低并行度、减少日志交错和内存压力,不会自动修复依赖关系。-k 会允许 Ninja 在 达到指定失败数量前继续,适合发现同一批变更造成的多个独立问题;它也会产生更多级联错误,首次定位 根因时不宜直接使用无限继续。
Android 17 的 config 测试明确覆盖了 -j 与 -k 的解析,runNinja 再把非默认 keep-going 值转成 执行器的 -k 参数。这说明它们是受支持的构建入口参数,而不是直接调用底层 Ninja 的旁路技巧。
3.2 m nothing
m nothingnothing 不要求构建普通产品产物,常用于触发产品配置、Soong/Kati 图生成和必要的 Ninja 图检查。 如果它已经失败,问题多半位于环境、产品配置或构建图,而不是某个普通 C++ 对象文件的编译正文。
但它不是“Soong-only”:Android 17 的编排仍可能运行产品配置、Soong、Kati,并让 Ninja 处理图生成 相关目标。判断阶段仍要看日志中的失败 action 和命令。
3.3 action工具
对 Ninja 阶段错误,优先读取 $OUT_DIR/error.log。也可查询目标命令:
NINJA_ARGS="-t commands <target>" m或在已经知道输出节点时查询:
NINJA_ARGS="-t query <output>" m
NINJA_ARGS="-t inputs <output>" m这些命令的目的不是“多打印一点”,而是把失败输出反向映射到 rule、直接输入和执行命令。生成图本身 有问题时,subtool 可能无法运行,此时应回到 Android.bp、.mk 的文件行号和 Soong/Kati 日志。
4. 构建配置
4.1 配置失败
Build 在产品配置和模块图之前检查根目录异常文件、内存、输出目录和文件系统大小写敏感性。 因此没有出现 Android.bp、FAILED: 或 clang 命令,不代表“构建系统没有信息”,而可能是尚未进入 图生成阶段。
源码文件:build/soong/ui/build/build.go
相关函数/类型:checkCaseSensitivity
if string(res) != lowerData {
ctx.Println("You are building on a case-insensitive filesystem.")
ctx.Println("Please move your source tree to a case-sensitive filesystem.")
// 说明:这是致命预检,不是可忽略warning。
ctx.Fatalln("Case-insensitive filesystems not supported")
}如果源码或 OUT_DIR 位于大小写不敏感文件系统,同名异大小写路径可能折叠,构建会直接拒绝继续。 解决方向是使用大小写敏感的构建文件系统,而不是反复删除某个模块中间目录。
4.2 checkRAM
checkRAM 在检测到内存较少,或 job 数相对内存过高时建议降低 -j。真正的资源失败还可能表现为:
- 工具只打印
Killed或以信号退出; - 多个高内存 action 同时失败;
- 系统日志记录 OOM killer、内存压力或进程被终止;
- 降低并行度后同一源码能够稳定通过。
这些现象支持“资源压力”判断,但不能仅凭 exit code 猜测 OOM。应同时检查主机内存、swap、磁盘空间、 系统事件和 action 的 MaxRss/trace 信息。若 -j1 仍在同一源码位置稳定报语法或类型错误,根因通常 不在并发。
4.3 三元组
产品配置失败时先确认:
get_build_var TARGET_PRODUCT
get_build_var TARGET_RELEASE
get_build_var TARGET_BUILD_VARIANT常见根因包括 lunch 选择不存在、product makefile 未被发现、设备目录未同步、BoardConfig 条件不成立, 或者从另一终端继承了不匹配的 OUT_DIR。不要手工拼接一组 TARGET_* 变量代替 lunch; lunch目标与编译变体 已说明三元组如何进入构建环境。
5. Soong 错误
5.1 配置
Soong 错误常包含:
path/to/Android.bp:line:column: unrecognized property "..."
path/to/Android.bp:line:column: "consumer" depends on undefined module "provider"
module "..." already defined这类错误发生在构建图形成过程中。owner 通常是报错模块的 Android.bp 和对应模块类型实现;消费者 是依赖它的其他模块或后续生成器。没有可执行的 clang command,复制某条编译命令自然无法复现。
5.2 property
这表示当前模块类型的 property schema 不接受该字段。排查步骤是:
- 确认字段拼写、嵌套层级和模块类型;
- 在固定 tag 的 Soong 源码或
soong_docs中查属性,而不是参考浮动分支文档; - 检查属性是否只属于另一个模块类型或版本;
- 删除或迁移属性后用
m nothing先验证图生成。
android/package_test.go 构造了带非法 name、visibility 和 licenses 属性的 package 模块,fixture 断言三条 unrecognized property 错误都出现。这个测试证明 Soong 可以在一次分析中报告多个 schema 错误,也提醒读者:同一文件多条错误未必是级联假象,应逐项对照允许的 property。
5.3 module缺失
depends on undefined module 说明消费者创建了一条指向不存在 provider 的边,可能原因包括:
- 模块名拼写或命名空间错误;
- provider 所在 project 没有同步;
- provider 被条件配置排除;
- 当前 tag 已重命名或删除该模块;
- 依赖字段引用了文件名、库文件名,而不是 Soong 模块名。
先在源码树查定义:
rg -n 'name: "<provider>"' --glob 'Android.bp'
repo list | rg '<expected-project>'如果定义存在,再检查 namespace、visibility、enabled 条件和 variant;如果 project 不存在,应修正 manifest 或同步范围,而不是新建一个同名空模块掩盖缺口。
android/license_kind_test.go 的 fixture 让 other_notice 依赖不存在的 notice,并断言精确的 undefined module 错误。它证明错误文本指出的是“消费者 → provider”关系;它不证明任何生产项目中的缺失原因, 原因仍需结合源码定义和 manifest 判断。
5.4 duplicate
| 错误族 | 要检查的 owner | 不能直接采用的做法 |
|---|---|---|
already defined | 同一 namespace 中所有同名定义、prebuilt/source 选择 | 随意给其中一个模块加后缀,破坏消费者 |
not visible | provider 的 visibility 与 consumer package | 直接改成全局 public 而不审查边界 |
depends on disabled module | provider 的 enabled/target/product 条件 | 无条件打开所有 variant |
missing variant | arch、sdk、apex、image、linkage 等变体轴 | 只确认模块名存在就结束排查 |
android/package_test.go 还构造了同一 package 中两个 package {},断言 module "//top" already defined。测试输入和断言说明重复定义由图构建阶段拒绝,不需要等到 Ninja 执行才发现。
5.5 依赖缺失
Soong UI 只在显式环境为 true 时传入 ALLOW_MISSING_DEPENDENCIES。它主要服务特定构建或测试兼容 场景,会让某些缺失边转成延后失败或 error rule。日常开发中开启它可能把最清楚的图错误推迟到 Ninja 阶段,导致错误离根因更远。
6. Kati 错误
6.1 Kati
runKatiBuild 以 build/make/core/main.mk 为入口,并把 Soong 生成的 Make bridge 文件传入 Kati:
相关源码:
build/soong/ui/build/kati.gobuild/make/core/main.mk
源码文件:build/soong/ui/build/kati.go
相关函数/类型:runKatiBuild
args := []string{
"--writable", config.OutDir() + "/",
"--werror_implicit_rules",
"-f", "build/make/core/main.mk",
}
if !config.BuildBrokenDupRules() {
// 说明:重复定义target默认作为错误处理。
args = append(args, "--werror_overriding_commands")
}
args = append(args,
"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
)这意味着 Make 模块可以依赖由 Soong bridge 表达的模块,错误也可能横跨 .mk 与 .bp 边界。
6.2 首条致命错误
典型 Kati 诊断具有 .mk:行号:、***、Stop. 等特征,但仍应以 action command 确认。常见类别:
include路径不存在;- 产品或 BoardConfig 变量在读取时为空;
$(error ...)主动中止;- 同一 target 被多个规则定义;
- 使用被禁止的隐式规则;
$(shell ...)失败或产生不稳定结果。
调试临时变量时可使用 $(warning ...),但提交前要删除,并注意 Make 的立即求值 :=、延迟求值 = 与 inherit 顺序。不要通过长期关闭 --werror_implicit_rules 或 duplicate checks 来绕过结构问题。
6.3 环境重算
Soong UI 为实际使用过的环境变量保存状态。环境文件缺失、无效或变量变化时,构建可能重新分析;在 强制禁止 reanalysis 的模式下会直接失败并把变化写入 error.log。因此“昨天能编译,今天一开始就 重新生成或失败”时,要比较 lunch、release、OUT_DIR 和相关环境,而不是只看最近改动的 .cpp。
7. Ninja action
7.1 action输出
FAILED: out/.../obj/path/file.o
<完整编译命令>
path/to/file.cpp:123: error: ...FAILED: 后通常是 action 输出节点。真正的源码位置来自工具诊断或命令输入。对于生成代码,报错路径 可能位于 out/soong/.intermediates;此时要继续追踪生成它的 rule 和原始 .aidl、.proto、资源或 模板,不能直接编辑 out 中的生成文件。
7.2 头文件缺失
fatal error: 'foo/bar.h' file not found按以下顺序判断:
- 文件是否存在于当前源码树;
- command 的
-I/-isystem是否包含正确导出目录; - 消费者是否声明
header_libs、shared_libs、static_libs或生成头依赖; - provider 是否用
export_include_dirs或对应导出属性把头文件传给消费者; - 是否只在某个 arch、apex、host/device variant 下缺少依赖。
不要优先加入全局 include_dirs。它会扩大可见性、制造隐式依赖,并可能让本模块通过却破坏构建图的 可迁移性。正确 owner 通常是提供头文件的模块及消费者之间的显式依赖边。
7.3 链接符号
ld.lld: error: undefined symbol: namespace::Function(...)先区分:声明是否存在、定义是否被编译进某个库、该库是否在当前 linkage/variant 被链接、符号是否因 visibility 或条件宏未导出。可从失败 command 的 object、archive 和 shared library 参数入手,再用 目标架构对应的 llvm-nm/readelf 检查产物。
shared_libs、static_libs 和 whole_static_libs 语义不同;为了消除 undefined symbol 而随意改成 whole archive 可能引入重复符号、体积和初始化副作用。修复必须匹配符号所有权和链接模型。
7.4 编译器/API
C/C++、Java、Kotlin 和 Rust 的错误文本不同,但通用证据相同:
- 第一条来自本次修改或其直接展开结果的错误;
- 完整工具 command 与响应文件;
- 报错源码是手写文件还是生成文件;
- 当前模块 variant 和 feature flags;
- 修改前后接口声明与消费者是否同步。
大量 note:、模板实例化栈或 Java 级联错误不等于存在大量根因。先修复最早的语法、类型或缺失符号 错误,再重新构建确认后续错误是否自然消失。
7.5 生成器
Ninja 只是调度 action。运行 aapt2、AIDL、metalava、SELinux 编译器、镜像工具或自定义 genrule 时, 错误 owner 位于对应输入和 rule。判断方法是读 Command: 的第一个实际工具和参数,而不是把所有 FAILED: 都归入“编译器错误”。
对于 genrule,尤其要检查:
- 所有读取文件是否声明为 inputs;
- 所有生成文件是否声明为 outputs;
- command 是否依赖工作目录或未传递的环境变量;
- tool 是否通过
tools/tool_files声明; - 失败后是否留下部分输出,下一次构建会不会误判为有效。
Android 17 默认把 duplicate build、missing depfile 和 missing output 视为错误,目的就是阻止不完整 规则悄悄污染增量构建。兼容变量可以绕过部分检查,但应先修复 rule 的输入输出契约。
8. 并行执行
8.1 错误根因
并行执行时,A 先失败,B 和 C 仍可能打印缓冲输出;Ninja 随后停止调度,并取消未完成 action。 建议按以下优先级阅读:
error.log中第一个具有完整 command 和具体工具诊断的失败;- 与本次修改直接相关的 action;
- 多个失败 action 的共同输入或 provider;
- cancelled、stopped、overall failure 等汇总信息。
8.2 中断退出
用户按 Ctrl-C 时,中断本身可能是唯一原因;非用户中断下,NinjaReader.Close 会把仍运行 action 标成 action cancelled when ninja exited。应区分:
- 明确的人为中断:无需修改源码,重新运行同一目标验证;
- 某个 action 非零退出导致停止:修复该 action;
- executor 崩溃或主机杀进程:检查系统事件、磁盘和内存;
- 远程执行连接失败:进入对应 RBE/Siso 日志与认证边界,不能套用本地 clang 结论。
8.3 ninja_test.go
状态层可以按失败输出模式附加提示。ui/status/ninja_test.go 的测试证明:成功 action 不添加提示; 失败且命中模式时,在原始输出后附加第一条匹配提示;失败但不匹配时保持原输出。这说明 hint 是辅助 建议,不能覆盖工具原始诊断,也不能证明提示适用于当前代码。
9. 现象分流
这棵树的关键不是背错误关键词,而是找到状态 owner:产品配置由哪个 makefile 决定,模块边由哪个 Android.bp 声明,失败 action 由哪条 rule 生成,符号由哪个库提供,资源由哪个主机或 executor 消耗。只有 owner 明确,修复位置才不会停留在表象。
10. 四个典型排查过程
下面的日志片段是模式化示例,用于说明推理过程,不是某个固定产品的实测输出。
10.1 module复现
现象:
device/example/Android.bp:20:5: "demo" depends on undefined module "libfeature"过程:
m nothing可稳定复现,说明问题在图生成;- 搜索
name: "libfeature"; - 若无结果,用
repo list检查预期 project 是否存在; - 若定义存在,检查 namespace 和条件;
- 修正 manifest、依赖名或可见性后,再运行
m nothing; - 图通过后构建
m demo,确认消费者 variant 也存在。
不能用 ALLOW_MISSING_DEPENDENCIES=true 作为完成标准,因为这只改变失败时机,没有恢复 provider。
10.2 产物日志
现象:
fatal error: 'generated/demo.pb.h' file not found过程:
- 从
error.log读取失败 C++ command; - 查询输出
.o的 inputs,确认生成头是否进入依赖图; - 找到生成头的 gen/proto 模块和输出路径;
- 检查消费者声明的是生成头 provider,而不是只添加一个猜测的 include path;
- 构建 provider,再构建消费者;
- 修改原始
.proto后复建,确认消费者会因依赖变化自动重编。
最后一步验证的不只是“这次能编译”,还验证增量依赖契约已经修复。
10.3 链接复现
现象:
ld.lld: error: undefined symbol: demo::Run()过程:
- 读取链接 command,确认当前是 host/device、arch 和 shared/static 哪个 variant;
- 搜索声明与定义,确认定义没有被条件宏排除;
- 对 provider 产物执行目标工具链的符号查询;
- 检查 provider 是否真的出现在链接命令或响应文件;
- 修正显式库依赖或实现条件;
- 同时构建 provider 和直接 consumer,再构建更上层安装模块。
只在某个 variant 失败时,必须保留 variant 差异,不能用另一个能通过的 host 版本代替验证。
10.4 Killed
现象:工具没有源码诊断,只留下 Killed、信号或多个 cancelled action。
过程:
- 查第一个非 cancelled 失败和主机系统事件;
- 查看磁盘、内存、swap 和 trace;
- 使用相同目标
m -j1 <target>复现; - 如果低并发稳定通过,再逐步增加 job 数确认资源边界;
- 如果低并发仍稳定在相同源码报错,回到具体工具诊断;
- 记录可复现的并发、机器资源和峰值,而不是写“偶现,clean 后好了”。
11. 最小恢复策略
11.1 优先修复输入
构建图是从源码与配置派生的。通常修复顺序是:
- 修改错误的
.bp、.mk、源码或生成器输入; - 重跑原失败目标;
- 构建直接消费者;
- 必要时再构建安装模块或镜像;
- 只有信息表明某个派生文件损坏时,才清理对应最小范围。
不要把删除整个 out/ 作为首选。全量清理会让下一次构建重新计算大量无关 action,也会掩盖增量 规则缺陷:如果每次都必须 clean 才正确,真正的问题可能是 rule 缺失输入、输出或 restat 契约。
11.2 局部清理
局部清理适用于明确的派生状态异常,例如:
- 工具被中断后留下不可被原子替换的损坏输出;
- 生成规则变更但旧输出没有被声明为 obsolete;
- 响应文件或中间目录来自已删除的模块结构;
- 有确定信息表明缓存复用了错误结果。
清理前先用 outmod、失败 Outputs: 和 Ninja query 确认目标。不要使用模糊 glob 删除宽泛目录;清理 后还要解释为什么新规则能防止问题再次出现。
11.3 缓存
如果启用了 ccache,可按 ccache加速编译 的方法在禁用缓存后复现。 缓存关闭后错误消失,说明需要审查 key、sloppiness 和缓存内容;它不自动证明源码正确。
Siso/RBE 下还要区分本地复现是否使用相同工具链、输入根和环境。远程 action 失败不能只用本地主机 资源解释,必须查看远程执行状态和对应 action 日志。
12. 修复后验证
一次失败消失不等于问题已经解决。至少完成:
| 验证 | 要证明什么 |
|---|---|
m nothing | 产品配置和构建图能够生成 |
| 原失败目标 | 同一 action 在相同 variant 下通过 |
| 直接消费者 | 修改后的接口、头文件或库依赖能够被消费 |
| 增量再构建 | 没有无故重编,也没有修改后漏编 |
| 禁用临时旁路 | 不依赖 ALLOW_MISSING、兼容开关或个人缓存 |
| 安装产物/上层目标 | 修复不仅生成中间文件,还到达真实消费边界 |
对于构建图变更,可修改一个真实输入,再用:
NINJA_ARGS="-d explain" m <target>检查预期 action 为什么变 dirty。对于模块与安装产物关系,可用 refreshmod、pathmod、outmod 验证; 但在图生成尚未成功时,旧 module-info.json 可能已经过期,不能用它证明新模块存在。
提交问题报告或请求协助时,至少附上:
基线:release/tag + 相关project commit
目标:product-release-variant + m目标
复现命令:完整命令与必要环境
失败阶段:预检 / 产品配置 / Soong / Kati / Ninja action
失败action:Description、Outputs、Command、Output
首次出现:修改或同步动作
已验证:m nothing、-j1、缓存开关、相关project是否存在
边界条件:本地或远程executor、缓存状态和宿主机资源如果读者能够从一份 error.log 判断 action 的真实工具,从 Android.bp 或 .mk 找到配置 owner, 解释第一个失败与 cancelled 级联的关系,并在不全量 clean 的情况下完成同目标回归,那么这套排查方法 已经形成闭环。后续遇到具体语言或子系统错误时,仍沿用相同顺序:保留证据、确定边界、找到 owner、 最小复现、验证消费者。
