关于 build-error-analyzer
build-error-analyzer 是面向 OpenHarmony 开发者的构建错误分析技能。它先从 build.log 提取最新错误到 last_error.log,再结合已知案例提供修复建议,但不会自动修改代码。
主要功能
- 01提取最新构建错误块
- 02仅分析 last_error.log
- 03按类别分析编译与链接错误
- 04匹配历史案例和错误模式
- 05提供根因、修复建议及验证步骤
适用场景
- 01定位 OpenHarmony 最新构建错误
- 02分析 SDK 构建失败
- 03为链接错误查找可能原因
- 04获取人工应用的修复建议
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ openharmonyinsight/ openharmony-skills --skill build-error-analyzer安装后在 Claude Code 中输入/build-error-analyzer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdexamples/USAGE_EXAMPLES.mdreferences/build-system-ace-core-ng-source-set.mdcase-std-function-refptr-forward-declaration.mdforward-declaration-refptr-member.mdforward-declaration-struct-helper-method.mdlto-virtual-thunk-libace-map-export.mdmingw-dllexport-declaration-mismatch.mdnamespace-ambiguous-type-resolution.mdredefinition-error-constexpr.mdsymbol-export-ace-force-export.mdsymbol-export-libace-map.mdtemplate-instantiation-type-export.mdtest-missing-source-files.mdundefined-symbol-missing-cpp.mdscript/extract_last_error.sh详细介绍
构建错误分析技能
此技能专门用于分析 OpenHarmony 的 last_error.log 中的构建错误,并根据错误模式和历史案例提供修复建议(仅提供建议,不会自动修改代码)。
重要:此技能仅提供分析和建议,不会自动修改代码文件。
⚠️ 关键工作流(必须遵循)
必须始终严格遵循以下顺序:
- 首先:从
out/<product>/build.log提取错误 → 生成out/<product>/last_error.log - 然后:仅从
out/<product>/last_error.log读取并分析 - 绝不:直接读取
out/<product>/build.log或任何其他日志文件(error.log、build_output*.log 等) - 成功情况:如果
last_error.log包含“build success”或“no error”,请停止并报告成功
原因:
build.log包含完整的构建历史(数千行)last_error.log仅包含最近的错误块(由脚本提取)- 直接读取
build.log会导致分析过时或无关的错误 - 提取脚本可确保你始终分析最新的错误
- 其他日志文件(error.log、build_output*.log)可能包含先前构建中的过时错误
读取任何错误之前,始终运行:
# ⚠️ CRITICAL: First cd to OpenHarmony root directory, then run the script
cd <openharmony_root>
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/<product>/build.log
⚠️ 关键——先检查是否成功:
读取 last_error.log 后,如果你看到:
build success, no errorno error- 或类似的成功消息
立即停止并报告:
## ✅ 构建成功
最新的构建已经成功完成,没有发现任何错误。
**构建状态**: 成功
**错误信息**: 无
建议: 您可以继续进行开发或测试工作。
不要:
- ❌ 继续在其他日志文件中查找错误
- ❌ 读取 error.log
- ❌ 读取 build_output*.log 文件
- ❌ 查找历史错误
重要路径信息
始终使用以下路径:
- 提取脚本:
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh(相对于 OpenHarmony 根目录) - 源构建日志:
out/<product>/build.log(例如out/rk3568/build.log)——不要直接读取 - 目标错误日志:
out/<product>/last_error.log(在与 build.log 相同的目录中自动生成)——只读取此文件
⚠️ 特殊情况——SDK 构建路径:
- SDK 构建日志:
out/sdk/build.log(不是out/ohos-sdk/build.log) - SDK 错误日志:
out/sdk/last_error.log(不是out/ohos-sdk/last_error.log) - SDK 产品例外:SDK 使用
out/sdk/目录,而不是out/ohos-sdk/ - 触发关键词:“分析SDK的编译错误”、“analyze SDK build errors”、“check SDK errors”
工作流:
- ✅ 使用脚本从
out/<product>/build.log提取错误 → 创建out/<product>/last_error.log - ✅ 对于 SDK:从
out/sdk/build.log提取 → 创建out/sdk/last_error.log - ✅ 仅分析提取后的
last_error.log中的错误 - ✅ 仅提供修复建议——不要修改代码
应避免的常见错误:
- ❌ 直接读取
build.log(会得到过时或错误的错误信息) - ❌ 读取
error.log(包含先前构建中的过时错误) - ❌ 读取其他日志文件(build_output*.log、error.*.log 等)
- ❌ 当
last_error.log显示“build success”时仍继续查找错误 - ❌ 未执行提取步骤就分析错误
- ❌ 从错误目录运行提取脚本(必须从 OpenHarmony 根目录运行)
- ✅ 始终先切换到
<openharmony_root>目录 - ✅ 始终先提取,然后仅读取
last_error.log - ✅ 如果
last_error.log显示成功,就停止并报告成功
行为准则
此技能仅提供分析和建议:
✅ 应做:
- 从构建日志中提取错误
- 分析错误并进行分类
- 将错误与已知模式进行匹配
- 提供详细的修复建议,包括文件路径和行号
- 展示修改前后的代码示例
- 说明根本原因
- 建议验证步骤
❌ 不得做:
- 使用 Edit 工具修改代码文件
- 使用 Write 工具创建或修改文件
- 自动运行构建命令
- 未经用户确认就应用修复
- 对代码库进行任何更改
- 读取
last_error.log以外的日志文件(error.log、build_output*.log、error.*.log等) - 构建成功后继续查找错误
⚠️ 关键事项——绝不要建议清除 LTO 缓存:
- ❌ 绝不要建议清除
thinlto-cache目录 - ❌ 绝不要建议清除
llvmcache-*目录 - ❌ 绝不要建议删除
out/obj目录 - ❌ 绝不要建议删除整个
out/目录 - ❌ 绝不要将构建错误归咎于 LTO 缓存
- ❌ 绝不要将“清理后重新构建”作为首选方案
原因:用户应手动审核并应用修复,以便掌控代码更改并了解修改内容。LTO 缓存问题极为少见,将清除缓存作为首选方案会掩盖真正的问题、浪费重建时间,并影响增量编译的优势。
前提条件
- ⚠️ 关键事项:读取任何内容前,务必先从
out/<product>/build.log提取错误 - 错误日志位置:此技能仅从
out/<product>/last_error.log(与 build.log 位于同一目录)读取错误 - 错误提取:读取前,务必使用
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh中的提取脚本(从 OpenHarmony 根目录运行),生成或更新last_error.log - 源 build.log:错误仅从 OpenHarmony 输出目录中的
out/<product>/build.log提取 - 代码库上下文:可从任意 OpenHarmony 目录(ace_engine、根目录等)执行
- ⚠️ 成功检查:如果
last_error.log包含“build success”或“no error”,请立即停止并报告成功——不要读取error.log、build_output*.log等其他日志文件 - 唯一日志来源:仅从
last_error.log读取——绝不要读取error.log、build_output*.log、error.*.log或任何其他日志文件
分析工作流
步骤 1:提取最新错误(必需——务必首先执行)
⚠️ 关键事项:此步骤为必需步骤,必须在读取任何错误之前执行。
必需原因:
build.log包含完整的构建输出(新旧错误混杂)- 直接读取会分析过时或无关的错误
- 提取脚本只会提取最近的错误块
last_error.log会生成在与build.log相同的目录中
⚠️ 重要提示:务必先切换到 OpenHarmony 根目录,再运行脚本:
# Step 1: Navigate to OpenHarmony root directory
cd <openharmony_root>
# Step 2: Run the extraction script (relative path from root)
# Example for rk3568 product - extract errors from out/rk3568/build.log
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
# Example for SDK product - extract errors from out/sdk/build.log (⚠️ SPECIAL CASE: out/sdk/ NOT out/ohos-sdk/)
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/sdk/build.log
# Example for other products:
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3588/build.log
脚本将会:
- 仅从
out/<product>/build.log提取最新的错误块 - 输出到
out/<product>/last_error.log(同一目录) - 显示错误块大小和摘要
- 如果已有
last_error.log,则覆盖该文件
⚠️ 不要跳过此步骤
- 即使
last_error.log已存在,也要重新提取以获取最新错误 - 不要直接读取
build.log - 不要读取其他日志文件(
build_output*.log、error.log、error.*.log等) - 提取后仅分析
last_error.log
步骤 2:读取并分类错误
⚠️ 严重:仅读取 OpenHarmony 输出树中 out/<product>/ 目录下的 last_error.log。
提取后,错误日志位于:
out/<product>/last_error.log(与 build.log 位于同一目录)
读取提取出的错误日志:
# Check for last_error.log in common product directories
cat out/rk3568/last_error.log
# or for SDK (⚠️ SPECIAL CASE: out/sdk/ NOT out/ohos-sdk/)
cat out/sdk/last_error.log
# or for other products
cat out/rk3588/last_error.log
⚠️ 严重 - 首先检查是否成功:
进行任何分析之前,请检查构建是否成功:
如果 last_error.log 包含:
build success, no errorno error- 或类似的成功消息
立即停止并报告:
## ✅ 构建成功
最新的构建已经成功完成,没有发现任何错误。
**构建状态**: 成功
**错误信息**: 无
建议: 您可以继续进行开发或测试工作。
不要继续:
- ❌ 在 error.log 中搜索错误
- ❌ 在 build_output*.log 中搜索错误
- ❌ 在 error.*.log 文件中搜索错误
- ❌ 查找历史错误
- ✅ 停止并报告成功
只有在存在实际错误时,才继续进行分类:
错误类别:
- 编译错误(CXX 任务)- 语法错误、缺少头文件、类型不完整
- 链接器错误(SOLINK/LINK 任务)- 未定义符号、缺少库
- 构建系统错误 - GN/Ninja 配置问题
步骤 3:与已知案例匹配
将错误模式与 references/ 目录中的历史案例进行比较,并提供具体解决方案。
案例匹配优先级:
- 按错误特征匹配(例如,“undefined symbol” + “referred by”)
- 按错误上下文匹配(例如,“timepicker module” 的用法)
- 按文件模式匹配(例如 BUILD.gn 问题)
- 如果没有匹配的具体案例,则提供一般性指导
步骤 4:提供修复建议(不自动修改)
严重:此步骤仅提供建议。不要使用 Edit/Write 工具修改代码。
根据错误类型和匹配到的案例,提供:
- 根本原因分析
- 需要修改的具体文件(包含准确的行号和代码片段)
- 构建系统配置更改(包含准确的 GN 文件路径)
- 验证步骤
建议的输出格式:
## 构建错误分析
### 错误类型
[编译错误/链接错误/构建系统错误]
### 错误位置
[文件路径:行号]
### 错误信息
[完整的错误消息]
### 根本原因
[详细解释为什么会出现这个错误]
### 修复建议
**文件**: [完整文件路径:行号]
**当前代码**:
```cpp
[错误代码片段]
应修改为:
[正确代码片段]
修改说明:[解释为什么要这样修改]
验证步骤
- [第一步验证操作]
- [第二步验证操作]
- [如何确认修复成功]
参考
[相关的历史案例或文档链接]
**DO NOT**:
- ❌ Use Edit tool to make changes
- ❌ Use Write tool to create/modify files
- ❌ Run build commands automatically
**DO**:
- ✅ Provide clear recommendations with file paths and line numbers
- ✅ Show before/after code snippets
- ✅ Explain why the fix works
- ✅ Suggest verification commands
## Error Patterns and Solutions
### Pattern 1: Undefined Symbol Errors
**Error signature:**
ld.lld: error: undefined symbol: <symbol_name>
referenced by :
**⚠️ CRITICAL ANALYSIS WORKFLOW (MUST FOLLOW IN ORDER):**
When encountering `ld.lld: error: undefined symbol:` linker errors, you MUST follow this sequence:
#### Step 1: Identify Which Library is Failing to Link
Check the error message to determine which library has the undefined symbol:
- Look for the target being built (e.g., `libace.z.so`, `libace_compatible.z.so`, `libarkoala_native_ani.so`, `libace_ndk.z.so`)
- Check which library is referencing the symbol (`>>> referenced by`)
#### Step 2: Apply the Correct Scenario Based on Library Type
**Scenario 1: libace/libace_compatible Main Library Cannot Find Symbol**
**Symptoms:**
- Error occurs when linking `libace.z.so` or `libace_compatible.z.so`
- Symbol is undefined in the main library itself
**Root Cause:**
- Symbol implementation is missing or not compiled
- Method exists but .cpp file not in BUILD.gn
**Solution:**
1. **Check if implementation exists:**
```bash
grep -r "SymbolName" --include="*.cpp" frameworks/
-
如果实现已存在,请检查 BUILD.gn:
grep -r "implementation_file.cpp" frameworks/*/BUILD.gn -
将 .cpp 添加到 BUILD.gn:
- 查找同一目录中其他文件的添加位置
- 将缺少的 .cpp 添加到合适的 source_set
- 重要:参考同一目录中的文件,以确定正确的 source_set
- 不要假设所有文件都应添加到
ace_core_ng_source_set
-
通过其他文件验证:
# Find where similar files in same directory are compiled find frameworks/core/components_ng/pattern/<pattern_name>/ -name "*.cpp" grep "similar_file.cpp" frameworks/core/BUILD.gn
场景 2:其他依赖 libace/libace_compatible 的库链接
症状:
- 链接依赖
libace或libace_compatible的库时发生错误 - GN 文件显示
deps包含 libace/libace_compatible - 符号应来自主库
根本原因:
- 符号存在于主库中,但未导出
- 缺少 ACE_FORCE_EXPORT 宏
- 符号不在 libace.map 白名单中
⚠️ 严重:首先区分非模板和模板
在应用任何解决方案之前,请确定类型:
undefined symbol error
↓
Is it a non-template class method?
→ YES: Use Non-Template Solution (Step 1A)
→ NO: Use Template Solution (Step 1B, see Scenario 3)
解决方案(分步操作):
步骤 1A:针对非模板类方法 ⭐
⚠️ 检查 1:类是否具有 ACE_FORCE_EXPORT?
// Case A: Class WITHOUT ACE_FORCE_EXPORT
class PaddingPropertyF {
float Width() const;
};
// Case B: Class WITH ACE_FORCE_EXPORT
class ACE_FORCE_EXPORT PaddingPropertyF {
float Width() const;
};
对于案例 A(类没有导出)- 首选方法:
- ✅ 在头文件(.h 文件)中的方法声明上添加
ACE_FORCE_EXPORT - ❌ 不要在 .cpp 文件中的方法定义上添加
- 原因:实现细粒度的导出控制,明确 API 边界
示例(PaddingPropertyF 案例):
// In header file (.h) - Add ACE_FORCE_EXPORT to method declarations
class PaddingPropertyF {
ACE_FORCE_EXPORT float Width() const; // ← Add here
ACE_FORCE_EXPORT float Height() const; // ← Add here
};
// In implementation file (.cpp) - NO ACE_FORCE_EXPORT here
float PaddingPropertyF::Width() const
{
return left.value_or(0.0f) + right.value_or(0.0f);
}
float PaddingPropertyF::Height() const
{
return top.value_or(0.0f) + bottom.value_or(0.0f);
}
对于案例 B(类已导出):
- ✅ 无需额外导出 - 类级导出已涵盖所有方法
- ❌ 不要在单个方法上添加
ACE_FORCE_EXPORT - 原因:整个类已导出
示例:
// In header file (.h) - Class already exported
class ACE_FORCE_EXPORT PaddingPropertyF { // ← Entire class exported
float Width() const; // Automatically exported
float Height() const; // Automatically exported
};
// In implementation file (.cpp) - No individual exports needed
float PaddingPropertyF::Width() const { /* implementation */ }
步骤 1B:针对模板类方法/函数(参见场景 3)
适用模板专属规则 - 详细步骤请参阅场景 3。
步骤 2:将符号名称细化后添加到 libace.map ⭐
添加导出后,将符号添加到 build/libace.map:
# Check if symbol already in libace.map
grep "ClassName" build/libace.map
所需更改:
- ⭐ 始终优先使用细粒度符号模式——导出特定方法,而非整个类
- 将符号模式添加到
build/libace.map白名单 - 尽可能使用方法级粒度
示例——常规方法:
# In build/libace.map
{
global:
# ✅ FINE-GRAINED - Export specific methods (PREFERRED for non-template)
OHOS::Ace::PaddingPropertyF::Width*;
OHOS::Ace::PaddingPropertyF::Height*;
# ⚠️ ACCEPTABLE - Export entire class (if class has ACE_FORCE_EXPORT)
OHOS::Ace::PaddingPropertyF::*;
# ⚠️ DIFFERENT - Template patterns (use wildcards)
void?OHOS::Ace::StringUtils::StringSplitter*; # Template function
OHOS::Ace::NG::LayoutConstraintT*; # Template class
};
}
为什么非模板要使用细粒度模式?
- ⭐ 最大限度缩小导出的符号范围——只导出所需内容
- ⭐ 明确 API 边界——明确哪些符号是公开的
- ⭐ 减少符号冲突——导出范围越小,冲突越少
- ⭐ 更好地跟踪依赖关系——便于查看其他模块使用了哪些内容
- ⭐ 安全性——限制导出符号的攻击面
常见错误(请勿这样做):
- ❌ 只需要特定方法时使用
ClassName::*; - ❌ 导出外部未使用的私有方法
- ❌ 忘记构造函数通配符会匹配所有重载
步骤 3:验证符号导出:
nm -D out/rk3568/arkui/ace_engine/libace.z.so | grep SymbolName
- 如果未显示符号,则导出失败
- 添加 ACE_FORCE_EXPORT 和 libace.map 条目后重新构建
常见错误(请勿这样做):
- ❌ 只需要特定方法时,请勿使用
ClassName::*;(⚠️ 常见错误) - ❌ 仅构造函数未定义时,请勿导出所有类成员
- ❌ 请勿混用非模板规则和模板规则
- ❌ 非模板方法请勿在 .cpp 中添加导出
- ❌ 类已有
ACE_FORCE_EXPORT时,请勿添加单独的方法导出 - ❌ 请勿直接使用
__attribute__((visibility("default")))
⚠️ 常见错误示例——过度导出:
// ❌ WRONG - Exports entire class when only constructors needed
OHOS::Ace::VelocityTracker::*; // Exports ALL members unnecessarily
// ✅ CORRECT - Fine-grained constructor export
OHOS::Ace::VelocityTracker::VelocityTracker*; // Only constructors
各模式的使用时机:
- 对特定方法使用
ClassName::MethodName*; - 对所有构造函数重载使用
ClassName::ClassName*; - 仅当大多数或所有成员都要导出时,才使用
ClassName::*;(很少见)
场景 3:模板函数实例化问题
症状:
- 未定义符号是模板函数
- 模板使用前向声明,并在 .cpp 中显式实例化
- 错误显示缺少特定模板特化(例如
StringSplitter<Color>、TransformStrCase<std::string>)
根本原因:
- 未显式实例化新的特化
- 模板声明或实现缺少导出宏
- libace.map 缺少模板实例化对应的模式
解决方案(需要 3 个步骤):
步骤 1:在头文件(.h)中添加 extern template 声明
为新的特化添加显式模板声明:
// In header file (.h)
extern template void StringSplitter<Color>(const std::string& source, char delimiter,
Color (*)(const std::string&), std::vector<Color>& out);
步骤 2:在模板函数声明中添加 ACE_FORCE_EXPORT
// In header file (.h) - Template function declaration
template<class T>
ACE_FORCE_EXPORT
void StringSplitter(
const std::string& source, char delimiter, T (*func)(const std::string&), std::vector<T>& out);
步骤 3:在 .cpp 中添加显式实例化
// In implementation file (.cpp)
template void StringSplitter<Color>(const std::string& source, char delimiter,
Color (*)(const std::string&), std::vector<Color>& out);
步骤 4:在模板实现中添加 ACE_FORCE_EXPORT
// In implementation file (.cpp) - Template function implementation
template<class T>
ACE_FORCE_EXPORT
void StringSplitter(
const std::string& source, char delimiter, T (*func)(const std::string&), std::vector<T>& out)
{
// implementation
}
步骤 5:添加到 libace.map 白名单
# In build/libace.map
{
global:
OHOS::Ace::StringUtils::*;
void?OHOS::Ace::StringUtils::StringSplitter*; # Match template instantiations
void?OHOS::Ace::StringUtils::TransformStrCase*; # Match template instantiations
};
⚠️ 模板特别注意事项:
- 声明和实现都必须添加导出宏
- 显式实例化必须放在 .cpp 文件中
- libace.map 模式必须使用
*通配符来匹配模板实例化 - 模式示例:
void?OHOS::Ace::StringUtils::StringSplitter*;
场景 4:特殊库(少见情况)
症状:
- 库不依赖 libace/libace_compatible
- 但使用主库中的工具函数(这些函数以前是内联的,现在放在 .cpp 中实现)
- 仅适用于以下特定库:
libarkoala_native_ani.solibace_ndk.z.so
根本原因:
- 工具函数从内联实现改为 .cpp 实现
- 由于库未链接主库,因此无法使用该符号
- 这是需要本地编译的特殊情况
解决方案:
- 将工具函数对应的 .cpp 文件直接添加到该库的 BUILD.gn sources 中
libarkoala_native_ani 示例:
# In: frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/ani/native/BUILD.gn
ohos_shared_library("arkoala_native_ani") {
sources = [
"//foundation/arkui/ace_engine/frameworks/base/utils/string_utils.cpp",
"UINode/uinode_module_methods.cpp",
# ... other sources
]
}
ace_ndk 示例:
# In: interfaces/native/BUILD.gn
ohos_shared_library("ace_ndk") {
sources = [
"//foundation/arkui/ace_engine/frameworks/base/utils/string_utils.cpp",
"//foundation/arkui/ace_engine/frameworks/core/accessibility/native_interface_accessibility_impl.cpp",
# ... other sources
]
}
⚠️ 重要:
- 此解决方案仅适用于
libarkoala_native_ani和ace_ndk - 请勿在其他库中使用此模式
- 这些库使用工具函数,但不会链接主库
场景 5:模板类方法导出问题 症状:
- 链接依赖
libace或libace_compatible的库时发生错误 - 未定义符号是一个显式实例化的模板类方法
- 错误显示:
undefined symbol: ClassName<T>::MethodName(params) - 模板类的结构体声明上有
ACE_FORCE_EXPORT,但方法仍未导出
错误示例:
ld.lld: error: undefined symbol: OHOS::Ace::NG::LayoutConstraintT<float>::UpdateSelfMarginSizeWithCheck(OHOS::Ace::NG::OptionalSize<float> const&)
>>> referenced by slider_layout_algorithm.cpp:212
根本原因:
- 模板类的结构体声明上有
ACE_FORCE_EXPORT(例如,struct ACE_FORCE_EXPORT LayoutConstraintT) - 但 .cpp 文件中的单个方法实现缺少
ACE_FORCE_EXPORT宏 - 模板显式实例化不会自动导出单个方法
- 每个需要导出的方法实现都必须添加该宏
解决方案:
步骤 1:在 .cpp 中的方法实现上添加 ACE_FORCE_EXPORT
为每个需要导出的模板方法实现添加 ACE_FORCE_EXPORT:
// In implementation file (.cpp)
template<typename T>
ACE_FORCE_EXPORT // ← Add this macro before return type
bool LayoutConstraintT<T>::UpdateSelfMarginSizeWithCheck(const OptionalSize<T>& size)
{
if (selfIdealSize == size) {
return false;
}
return selfIdealSize.UpdateSizeWithCheck(size);
}
template<typename T>
ACE_FORCE_EXPORT // ← Add this macro before return type
bool LayoutConstraintT<T>::UpdateMaxSizeWithCheck(const SizeT<T>& size)
{
if (maxSize == size) {
return false;
}
return maxSize.UpdateSizeWhenSmaller(size);
}
⚠️ 模板方法导出说明:
- 仅导出结构体声明是不够的:添加
struct ACE_FORCE_EXPORT LayoutConstraintT不会自动导出所有方法 - 每个方法都需要宏:.cpp 中每个需要导出的方法实现都必须添加
ACE_FORCE_EXPORT - 将宏放在返回类型之前:宏应放在
template<>和返回类型之间 - 头文件中不需要:不要在头文件的方法声明上添加
ACE_FORCE_EXPORT宏(结构体声明已足够)
步骤 2:确认 libace.map 中有通配符模式
检查 libace.map 是否包含带通配符的模板类:
# Check libace.map for template class pattern
grep "LayoutConstraintT" build/libace.map
预期输出:
OHOS::Ace::NG::LayoutConstraintT*;
⚠️ libace.map 通配符规则:
- 使用不带引号的通配符模式:
OHOS::Ace::ClassName::*; - 不要使用带引号的通配符模式:
"OHOS::Ace::ClassName<T>::*;"(通配符不会生效) - 不带引号的通配符会自动匹配所有模板实例化
- 在末尾使用
*以匹配所有方法:OHOS::Ace::NG::LayoutConstraintT*;
步骤 3:确认符号已导出
# Check if symbol is exported in shared library
nm -D out/rk3568/arkui/ace_engine/libace.z.so | grep LayoutConstraintT
预期输出(应显示已导出的符号):
0000000000000000 T _ZN2OHOS3Ace2NG14LayoutConstraintTIfE25UpdateSelfMarginSizeWithCheckERKNS0_11OptionalSizeIfEE
⚠️ 常见错误:
- ❌ 仅依赖
struct ACE_FORCE_EXPORT ClassName——不足以导出方法 - ❌ 忘记在 .cpp 中的每个方法实现上添加宏
- ❌ 在头文件的方法声明上添加宏(不必要,结构体声明已足够)
- ❌ 在 libace.map 中使用带引号的模式:
"ClassName<T>::*"不会匹配 - ❌ 在 libace.map 中添加特定的模板实例化:应改用通配符
✅ 正确做法:
- ✅ 在 .cpp 中的每个模板方法实现上添加
ACE_FORCE_EXPORT - ✅ 将宏放在
template<>和返回类型之间 - ✅ 在头文件中保留带有
ACE_FORCE_EXPORT的结构体声明 - ✅ 在 libace.map 中使用不带引号的通配符:
OHOS::Ace::NG::ClassName*;
步骤 3:验证修复
应用适当的解决方案后:
# 1. Navigate to OpenHarmony root and rebuild
cd <openharmony_root>
./build.sh --product-name rk3568 --build-target ace_engine
# 2. Check if symbol is now exported (for scenarios 2-3)
nm -D out/rk3568/arkui/ace_engine/libace.z.so | grep SymbolName
# 3. Extract new errors
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
# 4. Check result
cat out/rk3568/last_error.log
# For SDK builds (⚠️ use out/sdk/ directory):
# 1. Build SDK: ./build.sh --product-name ohos-sdk --ccache
# 2. Extract errors: foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/sdk/build.log
# 3. Check result: cat out/sdk/last_error.log
常见错误(请勿这样做):
⚠️ 严重警告 - 禁止清理缓存:
- ❌ 绝不要建议清理 thinlto-cache(99.9% 的情况下,这都不是问题所在)
- ❌ 绝不要建议清理 llvmcache-* 目录
- ❌ 绝不要建议删除 out/obj 目录
- ❌ 绝不要建议删除整个 out 目录
- ❌ 绝不要将“clean build”作为首选方案
- ❌ 清理缓存会掩盖真实问题,并浪费大量重建时间
- ✅ 务必分析实际错误并修复根本原因
其他常见错误:
- ❌ 不要跳过场景分析
- ❌ 不要使用
__attribute__((visibility("default")))代替 ACE_FORCE_EXPORT - ❌ 对于模板,不要忘记同时导出声明和实现
- ❌ 除指定的两个库外,不要使用场景 4(本地编译)
- ❌ 对于模板类方法,不要只依赖
struct ACE_FORCE_EXPORT ClassName(场景 5) - ❌ 不要在 libace.map 中使用带引号的通配符模式:
"ClassName<T>::*"不起作用(场景 5)
✅ 务必做到:
- ✅ 确定哪个库存在问题
- ✅ 检查实现是否存在且已编译
- ✅ 验证头文件声明是否使用 ACE_FORCE_EXPORT
- ✅ 检查 libace.map 白名单
- ✅ 对于模板:检查声明、实现和实例化
- ✅ 根据库类型使用正确的场景
- ✅ 对于模板类方法:在 .cpp 文件中为每个方法实现添加 ACE_FORCE_EXPORT(场景 5)
- ✅ 在 libace.map 中使用不带引号的通配符模式:
OHOS::Ace::NG::ClassName*;(场景 5)
模式 2:不完整类型错误
错误特征:
error: member access into incomplete type 'const ClassName'
error: invalid use of incomplete type 'class ClassName'
error: member access into incomplete type 'OHOS::Ace::Animator'
rawPtr_->IncRefCount();
常见原因:
- 缺少头文件包含
- 只有前向声明,没有完整定义
- RefPtr 作为类成员,但未进行适当的实现分离
- 头文件优化问题
⚠️ 特殊情况:RefPtr 作为类成员
当 RefPtr<T> 或 WeakPtr<T> 作为类成员导致不完整类型错误时:
不要只是将完整的头文件包含添加到头文件中。应当:
- 在头文件中保留前向声明:
class Animator; - 在头文件中声明特殊成员函数(不要使用
= default):AnimatableColor(); // Declaration only ~AnimatableColor(); // Declaration only AnimatableColor(const AnimatableColor& color); // Declaration only - 在 .cpp 文件中使用
= default实现这些函数:// animatable_color.cpp #include "core/animation/animator.h" // Full definition here AnimatableColor::AnimatableColor() = default; AnimatableColor::~AnimatableColor() = default; AnimatableColor::AnimatableColor(const AnimatableColor& color) = default;
参见:references/forward-declaration-refptr-member.md,了解详细解决方案
模式 3:重定义错误
错误特征:
error: redefinition of 'symbol_name'
常见原因:
- 多个翻译单元中存在多个定义
- 仅在头文件中定义,却缺少 inline/constexpr
- 违反 ODR
历史案例
所有案例均按问题类型整理在 references/ 目录中。
案例 1:未定义符号 - 缺少 .cpp 文件
位置:references/undefined-symbol-missing-cpp.md
错误特征:
ld.lld: error: undefined symbol: OHOS::Ace::TextTheme::Builder::Build
ld.lld: error: undefined symbol: OHOS::Ace::AdvancedTextStyle::GetGradient
常见原因:
- 新建了 .cpp 文件,但未将其添加到 BUILD.gn
- 文件已添加到组件 BUILD.gn,但未添加到 source_set
- libace.z.so 仅链接 ace_core_ng 库
解决方案:将 .cpp 添加到组件 BUILD.gn 和 frameworks/core/BUILD.gn 的 xx_ng_source_set 中
案例 2:符号导出 - 缺少 ACE_FORCE_EXPORT
位置:references/symbol-export-ace-force-export.md
错误特征:
ld.lld: error: undefined symbol: OHOS::Ace::NG::DialogTypeMargin::UpdateDialogMargin
>>> referenced by dialog_button.cpp (timepicker module)
常见原因:
- 其他模块使用了符号,但未将其标记为导出
- 头文件声明缺少 ACE_FORCE_EXPORT
解决方案:在头文件声明中添加 ACE_FORCE_EXPORT,并将其添加到 build/libace.map
案例 3:符号导出 - libace.map 白名单
位置:references/symbol-export-libace-map.md
错误特征:
ld.lld: error: undefined symbol: OHOS::Ace::NG::ClassName::MethodName
上下文:符号已使用 ACE_FORCE_EXPORT,但仍未导出
常见原因:
- 符号不在 build/libace.map 白名单中
- 版本脚本中的符号格式不正确
解决方案:使用正确的格式将符号添加到 build/libace.map:OHOS::Ace::NG::ClassName::MethodName*;
案例 4:重新定义 - inline constexpr
位置:references/redefinition-error-constexpr.md
错误特征:
error: redefinition of 'DRAG_BACKGROUND_OPACITY'
error: redefinition of 'URL_DISA_OPACITY'
常见原因:
- 常量同时在头文件(inline constexpr)和 .cpp 中定义
- 违反 ODR(单一定义规则)
解决方案:从 .cpp 中移除重复定义,只在头文件中保留 inline constexpr
案例 5:构建系统 - ace_core_ng_source_set
位置:references/build-system-ace-core-ng-source-set.md
错误特征:
ld.lld: error: undefined symbol
上下文:文件已存在于组件的 BUILD.gn 中,但符号仍未定义
常见原因:
- 文件未添加到 frameworks/core/BUILD.gn 中的 ace_core_ng_source_set
- libace.z.so 只链接 ace_core_ng 库
解决方案:将文件添加到 frameworks/core/BUILD.gn 中的 ace_core_ng_source_set 模板
案例 6:RefPtr 成员 - 前向声明优化
位置:references/forward-declaration-refptr-member.md
错误特征:
error: member access into incomplete type 'OHOS::Ace::Animator'
rawPtr_->IncRefCount();
note: in instantiation of member function 'OHOS::Ace::RefPtr<OHOS::Ace::Animator>::RefPtr'
上下文:将 RefPtr<Animator> 或 WeakPtr<T> 用作类成员变量
常见原因:
- 在头文件中使用
= default定义特殊成员函数(构造函数/析构函数/拷贝构造函数) - 编译器在头文件中实例化这些函数,因此需要完整的类型定义
- 常见错误:撤销前向声明并添加完整的 include
正确解决方案:不要撤销前向声明!使用实现分离:
- 在头文件中保留前向声明:
class Animator; - 在头文件中声明特殊成员函数(不带
= default) - 在 .cpp 文件中使用
= default实现这些函数 - 在 .cpp 文件中包含完整定义
优点:减少头文件依赖,加快编译速度(示例中提升了 57%)
案例 7:测试链接 - 缺少源文件
位置:references/test-missing-source-files.md
错误特征:
ld.lld: error: undefined symbol: OHOS::Ace::ClassName::MethodName(...)
>>> referenced by test_file.cpp:123
上下文:链接测试可执行文件(例如 xxx_unittest)
常见原因:
- 测试 BUILD.gn 的 sources 中缺少必需的 .cpp 文件
- 头文件优化将实现移到了 .cpp 中(例如 StringUtils、LogWrapper)
- 新的实现文件未添加到构建系统
常见缺失文件:
log_wrapper.cpp- LogWrapper 方法(JudgeLevel、GetBriefFileName、PrintLog)string_utils.cpp- StringUtils 方法(StringSplitter、TransformStrCase、StrToInt)layout_constraint.cpp- LayoutConstraintT 模板方法measure_property.cpp- PaddingPropertyF、MeasureProperty 方法
解决方案:根据测试模板类型,将缺失的 .cpp 文件添加到相应的 sources 中:
- ohos_unittest without ace_base:添加到测试的 sources 中
- ohos_unittest with ace_base:添加到 ace_base source_set 中
- ace_unittest:添加到 ace_base source_set 中(默认依赖)
⚠️ 重要:只添加源文件,不要修改 cflags/configs/defines
案例 8:结构体 RefPtr 成员——辅助方法模式
位置:references/forward-declaration-struct-helper-method.md
错误特征:
error: member access into incomplete type 'OHOS::Ace::PixelMap'
shadowInfo.pixelMap->GetPixelMapSharedPtr()
^
note: forward declaration of 'OHOS::Ace::PixelMap'
class PixelMap;
上下文:纯数据结构(POD 结构体)包含 RefPtr<T> 成员,需要通过 -> 访问
常见原因:
- 结构体包含
RefPtr<T>成员变量 - 代码需要对智能指针调用
->运算符 - 直接调用
pixelMap->Method()会触发类型不完整错误 - 无法使用案例 6 的方法,因为结构体没有自己的 .cpp 文件
根本原因:
RefPtr<T>::operator->()会返回LifeCycleCheckable::PtrHolder<T>PtrHolder的构造函数和析构函数需要完整的类型定义才能访问usingCount_- 纯数据结构没有自己的实现文件
解决方案:添加辅助方法来封装类型访问:
- 在结构体中声明辅助方法:
std::shared_ptr<::OHOS::Media::PixelMap> GetPixelMapSharedPtr() const; - 新建实现文件(例如
interaction_data.cpp) - 在 .cpp 文件中实现辅助方法,并包含完整的头文件:
#include "base/image/pixel_map.h" - 将用法更新为调用辅助方法,而不是直接使用
->访问 - 将新的 .cpp 文件添加到 BUILD.gn 源文件集
高级场景(辅助方法不足以解决时):
- 结构体有自己的 .cpp 文件,并且被
std::vector使用 std::vector::operator=会触发复制,因此需要完整类型- 解决方案:升级为完整的前向声明优化(类似案例 6)
- 请参阅
forward-declaration-struct-helper-method.md中的“高级场景”部分
要点:
- 使用完全限定名称
::OHOS::Media::PixelMap,避免命名空间混淆 - 在头文件中保留前向声明,只在 .cpp 文件中包含完整定义
案例 9:命名空间类型解析——缺少限定符
位置:references/namespace-ambiguous-type-resolution.md
错误特征:
error: no member named 'DragEvent' in namespace 'OHOS::Ace'
using OnDragStartFunc = std::function<DragDropBaseInfo(const RefPtr<OHOS::Ace::DragEvent>&, const std::string&)>;
~~~~~~~~~~~~~^
上下文:类型定义在父命名空间中,却在子命名空间中使用
常见原因:
- 类型
DragEvent定义在OHOS::Ace命名空间中 - 在
OHOS::Ace::NG子命名空间中使用该类型的代码 - git diff 显示命名空间前缀被删除(例如
- OHOS::Ace::DragEvent) - 有人移除了完全限定的命名空间前缀
根本原因:
- 类型定义在父命名空间中(例如
OHOS::Ace::DragEvent) - 却在子命名空间中使用(例如
OHOS::Ace::NG) - 删除命名空间前缀会导致编译器在错误的命名空间中搜索
- 在错误的命名空间中进行前向声明没有帮助
正确的解决方案(按优先级排序):
-
保留完整的命名空间限定符(推荐):
// In OHOS::Ace::NG namespace using OnDragStartFunc = std::function<DragDropBaseInfo( const RefPtr<OHOS::Ace::DragEvent>&, // ✅ Keep full namespace const std::string&)>; -
在正确的命名空间中添加前向声明:
// In header, before OHOS::Ace::NG namespace namespace OHOS::Ace { class DragEvent; // ✅ Forward declare in correct namespace } // In OHOS::Ace::NG namespace using OnDragStartFunc = std::function<DragDropBaseInfo( const RefPtr<OHOS::Ace::DragEvent>&, // Still need full namespace const std::string&)>; -
添加头文件依赖(需要完整类型时):
#include "core/components_ng/event/drag_event.h" // In OHOS::Ace::NG namespace using OnDragStartFunc = std::function<DragDropBaseInfo( const RefPtr<OHOS::Ace::DragEvent>&, // Still use full namespace const std::string&)>;
⚠️ 重要:
- 绝不要删除命名空间前缀
- 前向声明必须位于与类型定义相同的命名空间中
- 如果类型位于
OHOS::Ace中,前向声明也必须位于OHOS::Ace中 - 使用该类型的代码可以位于子命名空间中,但必须引用父命名空间
要点:
- 找到完整的类型定义(而不只是前向声明)
- 使前向声明的命名空间与类型定义的命名空间一致
- 优先保留完全限定名称,而不是添加 include
- 辅助方法封装对完整类型的访问
- 在保持 API 简洁的同时减少头文件依赖
适用场景:
- ✅ 包含智能指针成员的纯数据结构(POD 结构体)
- ✅ 需要通过
->或*解引用智能指针 - ✅ 无法向现有结构体添加 .cpp 文件
- ❌ 有自己的 .cpp 文件的类(请改用案例 6)
- ❌ 只需要构造函数或析构函数(请改用案例 6)
案例 9:LTO 虚拟 thunk - libace.map 导出
位置:references/lto-virtual-thunk-libace-map-export.md
错误特征:
ld.lld: error: undefined symbol: virtual thunk to OHOS::Ace::TouchEventTarget::~TouchEventTarget()
>>> referenced by ld-temp.o
>>> lto.tmp:(construction vtable for OHOS::Ace::TouchEventTarget-in-OHOS::Ace::V2::ListScrollBarController)
>>> referenced by ld-temp.o
>>> lto.tmp:(construction vtable for OHOS::Ace::TouchEventTarget-in-OHOS::Ace::VerticalDragRecognizer)
上下文:包含虚函数并用作基类的类,析构函数在头文件中声明、在 .cpp 文件中实现(前向声明优化),并启用了 LTO(链接时优化)
常见原因:
- 类包含虚函数并用作基类
- 前向声明优化:析构函数定义在 .cpp 文件中(非内联)
- LTO 为派生类创建虚拟 thunk
- 虚拟 thunk 符号未从库中导出
根本原因:
- LTO 在链接期间优化虚函数表
- 创建虚拟 thunk符号,以调整派生类的 this 指针
- 这些虚拟 thunk 符号必须在链接时可用
- 如果析构函数不是内联的,LTO 生成的虚拟 thunk 可能不会被导出
- 常见错误:改回内联析构函数(在头文件中使用
= default)
解决方案:保留前向声明优化,并添加 libace.map 导出项:
- 将析构函数保留在 .cpp 文件中:头文件中使用
~TouchEventTarget() override;,实现在 .cpp 文件中 - 添加到 libace.map:
OHOS::Ace::TouchEventTarget::*;- 导出该类的所有符号virtual?thunk?to?OHOS::Ace::TouchEventTarget::*;- 导出 LTO 虚拟 thunk
- 不要改回在头文件中内联定义
要点:
- 需要两种导出模式:
ClassName::*;和virtual?thunk?to::ClassName::*; - 通配符
*会匹配包括虚拟 thunk 在内的所有方法 - 保留前向声明优化
- 无需为了 LTO 而牺牲头文件优化
为什么有效:
- 编译阶段:头文件使用前向声明,.cpp 文件中包含实现
- LTO 链接阶段:LTO 优化虚函数表并创建虚拟 thunk
- 符号导出:libace.map 中的模式匹配虚拟 thunk,并将其导出到动态符号表
- 运行时:派生类虚函数表可以正确引用虚拟 thunk
适用场景:
- ✅ 包含虚函数且用作基类的类
- ✅ 使用前向声明优化(析构函数非内联)
- ✅ 链接器错误:"undefined symbol: virtual thunk to ClassName::~ClassName()"
- ✅ 使用 LTO(链接时优化)
- ❌ 未用作基类的类(不会有虚拟 thunk)
- ❌ 析构函数已在头文件中内联(无需保留优化)
- ❌ 未使用 LTO(传统编译不会创建虚拟 thunk)
常见错误:
- ❌ 改回内联析构函数(会失去前向声明优化)
- ❌ 只导出
ClassName::*;,未添加虚拟 thunk 模式 - ❌ 忘记添加 libace.map 条目
案例 10:MinGW - dllexport 声明不匹配
位置:references/mingw-dllexport-declaration-mismatch.md
错误特征:
error: redeclaration of 'OHOS::Ace::NG::PaddingPropertyT::SetEdges' cannot add 'dllexport' attribute
void PaddingPropertyT<T>::SetEdges(const T& leftValue, const T& rightValue, const T& topValue, const T& bottomValue)
^
note: previous declaration is here
void SetEdges(const T& leftValue, const T& rightValue, const T& topValue, const T& bottomValue);
^
上下文:仅在 MinGW/Windows 平台上编译时出现
主要特征:
- 仅在 MinGW/Windows 构建中出现(Linux/MacOS 上不会出现)
- 错误消息:"redeclaration cannot add 'dllexport' attribute"
- 头文件声明和 .cpp 实现的导出属性不一致
- 通常在为模板方法添加
ACE_FORCE_EXPORT后触发
常见原因:
- 头文件中的方法声明缺少
ACE_FORCE_EXPORT - 实现文件中有
ACE_FORCE_EXPORT,但头文件中没有 - 只有部分重载方法具有导出属性(不一致)
- MinGW 要求声明和定义严格一致
根本原因:
- MinGW DLL 导出规则要求声明和定义的导出属性完全匹配
- 如果头文件没有
__declspec(dllexport),但实现中有 → 编译失败 - 与对可见性属性要求较宽松的 Linux/MacOS 不同
解决方案:为头文件中的所有重载方法声明添加 ACE_FORCE_EXPORT:
// Header file - add to ALL declarations
ACE_FORCE_EXPORT void SetEdges(const T& padding);
ACE_FORCE_EXPORT void SetEdges(const T&, const T&, const T&, const T&); // ✅ Don't forget
ACE_FORCE_EXPORT bool operator==(const PaddingPropertyT& value) const;
ACE_FORCE_EXPORT bool operator!=(const PaddingPropertyT& value) const; // ✅ Don't forget
要点:
- 将其添加到头文件声明中(不只是 .cpp 实现)
- 确保所有重载方法都已标记(不要遗漏)
- 实现文件中也必须有
ACE_FORCE_EXPORT - 在 MinGW 平台上测试(Windows 构建)
适用场景:
- ✅ MinGW/Windows 编译错误
- ✅ 错误:"cannot add 'dllexport' attribute"
- ✅ 带有导出属性的模板方法
- ❌ 仅 Linux/MacOS(规则不同)
- ❌ 链接错误(这是编译错误)
预防检查清单:
- ✅ 确认是否需要导出(跨模块使用)
- ✅ 为所有头文件声明添加
ACE_FORCE_EXPORT - ✅ 为所有 .cpp 定义添加
ACE_FORCE_EXPORT - ✅ 检查所有重载版本(不要遗漏)
- ✅ 在 MinGW 平台上测试编译
案例 11:std::function<RefPtr> 前向声明
位置:references/case-std-function-refptr-forward-declaration.md
错误特征:
error: no member named 'DragEvent' in namespace 'OHOS::Ace'
using OnDragStartFunc = std::function<DragDropBaseInfo(const RefPtr<OHOS::Ace::DragEvent>&, const std::string&)>;
~~~~~~~~~~~^
上下文:std::function 模板使用 RefPtr<T> 作为参数类型
关键特性:
RefPtr<T>是固定大小的指针包装器using别名不会立即实例化模板- 由于
RefPtr<T>的大小已知,因此前向声明可行 - 实际的模板实例化发生在 .cpp 文件中
常见原因:
- 误以为
std::function需要完整的类型定义 - 不清楚模板实例化实际发生的时机
- 将类型别名声明与模板实例化混淆
根本原因:
RefPtr<T>大小固定(指针大小),因此前向声明可行using OnDragStartFunc = ...只是类型别名,并非实例化- 只有在 .cpp 中实际使用该类型时,才会实例化模板
解决方案:在头文件中使用前向声明,在 .cpp 中使用完整定义:
// Header file (gesture_event_hub.h)
namespace OHOS::Ace {
class DragEvent; // ✅ Forward declaration only
}
namespace OHOS::Ace::NG {
// Type alias - NO template instantiation here
using OnDragStartFunc = std::function<DragDropBaseInfo(
const RefPtr<OHOS::Ace::DragEvent>&, // ✅ Works with forward decl
const std::string&)>;
}
// Implementation file (gesture_event_hub.cpp)
#include "core/gestures/drag_event.h" // ✅ Full definition here
// Actual usage - template instantiation happens here
OnDragStartFunc callback = ...;
关键原则:
- RefPtr 只是指针包装器(大小固定)
- using 别名 ≠ 模板实例化
- 实际使用时才会在 .cpp 中实例化
- 头文件:前向声明即可
- 实现文件:需要完整定义
适用情况:
- ✅ 在头文件中使用
std::function<RefPtr<T>>(类型别名) - ✅ 在头文件中使用
std::vector<RefPtr<T>>(不实例化) - ✅ 声明返回或接收 RefPtr 的函数
- ❌ 在头文件中访问 T 的成员(需要完整定义)
- ❌ 使用 T 成员的内联方法(需要完整定义)
与相关案例的比较:
- 案例 6:RefPtr 作为类成员 → ✅ 前向声明可行
- 案例 8:结构体中的 RefPtr 与访问 → ⚠️ 需要辅助方法
- 案例 11:std::function<RefPtr> → ✅ 前向声明可行(本案例)
优势:
- 减少头文件依赖
- 加快编译速度
- 让命名空间边界更清晰
- 保留优化成果
实际案例:OpenHarmony ACE Engine——gesture_event_hub.h 优化成功地通过 std::function<RefPtr<OHOS::Ace::DragEvent>> 使用了前向声明
分析命令
检查符号是否存在
# Search for symbol definition in codebase
grep -r "SymbolName" --include="*.h" --include="*.cpp" frameworks/
# Check if .cpp file exists
find frameworks/ -name "implementation_file.cpp"
验证构建配置
# Check if file in BUILD.gn
grep -r "implementation_file.cpp" frameworks/*/BUILD.gn
# Check ace_core_ng source set
grep -A 100 "ace_core_ng_source_set" frameworks/core/BUILD.gn | grep "implementation_file.cpp"
检查符号导出
# Check if symbol exported in shared library
nm -D out/rk3568/arkui/ace_engine/libace.z.so | grep SymbolName
# Check libace.map
grep "SymbolName" build/libace.map
故障排查指南
对于新的 .cpp 文件
- 创建实现文件(.cpp)
- 将其添加到组件的 BUILD.gn
- 如果 libace.z.so 使用该文件,则将其添加到 frameworks/core/BUILD.gn ace_core_ng_source_set
- 如果符号被跨模块使用:
- 在头文件声明中添加 ACE_FORCE_EXPORT
- 将其添加到 build/libace.map 白名单
对于头文件优化问题
- 检查是否错误地使用了前向声明
- 确保在需要的位置提供完整类型
- 对仅限头文件的常量使用 inline constexpr
- 避免 ODR 违规
对于链接错误
- 从错误消息中确定缺失的符号
- 查找符号定义的位置
- 验证实现是否已编译(在 BUILD.gn 中)
- 如果跨模块使用,检查符号可见性/导出
- 验证链接顺序和依赖项
最佳实践
⚠️ 关键规则 - 绝不要清除 LTO 缓存或建议“干净构建”
⚠️ 绝对禁止 - 遇到任何构建错误时:
规则说明: 在任何情况下,都绝不要把清除 LTO 缓存、删除构建目录或执行“干净构建”作为首选或主要解决方案。
严格禁止的做法:
- ❌ 绝不要建议清除
thinlto-cache目录 - ❌ 绝不要建议清除
llvmcache-*目录 - ❌ 绝不要建议删除
out/obj目录 - ❌ 绝不要建议删除整个
out/目录 - ❌ 绝不要建议运行
rm -rf out/ - ❌ 绝不要建议“干净构建”或“从头重新构建”
- ❌ 绝不要把链接器错误归咎于 LTO 缓存
- ❌ 绝不要说“可能是缓存问题,试着清除缓存”
禁止这样做的原因:
- LTO 缓存问题极其罕见 - 99.9% 的构建错误都是代码或配置问题,而不是缓存问题
- 掩盖真正的问题 - 清除缓存只能暂时隐藏问题,无法修复根本原因
- 浪费大量时间 - 完整重建需要数小时;增量构建只需数分钟
- 失去增量编译的优势 - 后续构建会变慢
- 破坏可复现性 - 破坏构建状态,让调试更困难
- 诊断不充分 - 建议清除缓存说明没有充分分析错误
请记住:真正的构建错误都有真正的原因。找到原因并修复。不要归咎于缓存。
0. ⭐ 高优先级规则 — 解决头文件缺失错误
⚠️ 关键规则 — 遇到“找不到文件”或“没有此文件或目录”的头文件错误时:
规则说明: 如果编译因头文件缺失而失败,始终按以下优先顺序处理:
- 首选方案:将缺失的头文件添加到发生错误的 .cpp 文件中(而不是经过优化的头文件)
- 最后手段:只有在获得用户确认后,才考虑修改经过优化的头文件
为什么要遵守此规则?:
- 头文件优化工作会有意移除依赖项,以提升构建速度
- 盲目地重新添加已删除的依赖项会破坏优化效果
- .cpp 文件才是添加从头文件中移除的必要包含项的正确位置
- 这样可以保持接口(头文件)与实现(.cpp)之间的分离
如何检测经过优化的头文件:
在建议修改任何头文件之前,请检查该头文件是否处于优化状态:
# Check 1: Staged changes (暂存状态)
git status --porcelain | grep "^M.*\.h$"
# Check 2: Modified but not staged (修改状态)
git status --porcelain | grep "^ M.*\.h$"
# Check 3: Recent commit (最近一笔提交)
git log -1 --name-only --pretty=format:"" | grep "\.h$"
# Combined check - all three states:
git status --porcelain | grep "\.h$" # Staged or modified
git diff HEAD~1 --name-only | grep "\.h$" # Changed in last commit
如果头文件处于以下任一状态:
- ✅ 首选:将缺失的 #include 添加到发生错误的 .cpp 文件中
- ❌ 不要:建议将依赖项重新添加到经过优化的头文件中
- ⚠️ 最后手段:如果确实有必要修改头文件,必须先征求用户同意
何时添加到 .cpp 文件中(首选):
// ✅ CORRECT - Add to the .cpp file with the compilation error
// frameworks/core/components_ng/pattern/search/search_gesture_event_hub.cpp
#include "core/events/click_event.h" // Add missing header here
// ❌ WRONG - Do NOT add back to optimized headers
// frameworks/core/components_ng/event/click_event.h
// #include "core/pipeline/base/element.h" // Don't restore deleted dependencies
用户确认模板(可能需要修改头文件时):
⚠️ 检测到 [header_name.h] 可能正在被优化(状态:暂存/修改/最近提交)
编译错误:[error_message]
缺失头文件:[missing_header.h]
错误位置:[error_file.cpp:line]
建议方案:
✅ 方案1(推荐):在 [error_file.cpp] 中添加 #include "[missing_header.h]"
- 保持头文件优化成果
- 不增加头文件依赖
- 符合最佳实践
⚠️ 方案2(需确认):在 [header_name.h] 中添加回 #include "[missing_header.h]"
- 可能影响编译性能优化
- 需要用户确认必要性
请确认是否使用方案1,或说明为何需要方案2?
经过优化的头文件示例(未经确认请勿修改):
animation_utils.h— 动画工具函数click_event.h— 点击事件处理- 处于暂存/已修改状态或近期提交中涉及的任何头文件
检测工作流:
步骤 1:遇到“找不到文件”错误时
# Extract the missing header path from error
# Example: error: 'core/pipeline/base/element.h' file not found
步骤 2:检查引用该文件的文件是否为经过优化的头文件
# Find where this error occurs
grep -r "#include.*element.h" frameworks/core/components_ng/event/click_event.h
# Check if click_event.h is being optimized
git status --porcelain | grep "click_event.h"
git log -1 --name-only | grep "click_event.h"
步骤 3:应用适当的解决方案
- 如果头文件经过优化 → 添加到 .cpp 文件中
- 如果头文件未经优化 → 可以添加到头文件中
- 如果无法确定 → 向用户说明两种方案并征求意见
验证步骤:
- 检查 git 状态,查看是否有已暂存/已修改的头文件
- 检查最近一次提交中是否有头文件变更
- 找出发生编译错误的 .cpp 文件
- 将缺失的 #include 添加到该 .cpp 文件中(首选)
- 验证修复是否解决了错误
- 如果有必要修改头文件,请记录原因
关键原则:
- ✅ 保留头文件优化工作
- ✅ 将缺失的包含项添加到 .cpp 实现文件中
- ✅ 保持头文件依赖关系整洁
- ✅ 建议修改头文件之前检查 git 状态
- ❌ 不要盲目撤销优化工作
- ❌ 未检查优化状态前,不要建议将已删除的依赖项重新添加到头文件中
- ❌ 未经用户确认,不要修改处于暂存/已修改状态或近期提交中涉及的头文件
相关技能:
- header-optimization:了解头文件优化模式
- compile-analysis:分析头文件依赖关系
- 案例 6:RefPtr 成员前向声明优化
相关案例:
- 头文件优化工作(参见:header-optimization 技能)
- 前向声明模式(案例 6:RefPtr 成员优化)
- 编译错误分析(模式 2:不完整类型错误)
1. ⭐ 高优先级规则 - 类型命名空间混淆(PixelMap 等)
⚠️ 关键规则 - 遇到常见类名的类型不匹配错误时:
规则说明: 当错误消息指出常见类名(如 PixelMap)的类型不匹配时,务必确认你在相应代码位置使用了正确的命名空间变体。
常见问题 - PixelMap 命名空间混淆:
在 ACE Engine 框架中(frameworks/core/components_ng/ 等):
- ✅ 正确:
OHOS::Ace::PixelMap(base/image/pixel_map.h中的包装类) - ❌ 错误:
OHOS::Media::PixelMap(底层媒体类)
发生这种情况的原因:
Ace::PixelMap是对Media::PixelMap的包装- 专为 ACE Engine 框架使用而设计
- 位于:
base/image/pixel_map.h - IDE 自动补全可能会错误地建议使用
Media::PixelMap
解决方案模式:
-
确定错误位置:
- 检查文件是否位于
frameworks/目录中 - 确认是否使用了框架代码模式
- 检查文件是否位于
-
使用正确的类型:
// ✅ CORRECT for frameworks/ #include "base/image/pixel_map.h" RefPtr<PixelMap> pixelMap_; // OHOS::Ace::PixelMap // ❌ WRONG in frameworks/ #include "native_image/imageinfo.h" // Don't include media headers RefPtr<Media::PixelMap> pixelMap_; // Wrong namespace -
修复所有出现位置:
# Find wrong usage grep -rn "Media::PixelMap" frameworks/core/components_ng/ # Replace with correct type # Media::PixelMap → PixelMap (or OHOS::Ace::PixelMap)
适用此规则的情况:
- 错误指出常见名称(PixelMap 等)的类型不匹配
- 错误显示同一类型名称对应不同的命名空间
- 文件位于
frameworks/(而非interfaces/) - 返回类型导致虚函数重写错误
不要:
- ❌ 仅仅因为 IDE 建议使用
Media::PixelMap就认定它是正确的 - ❌ 添加
using Media::PixelMap;来修复命名空间问题 - ❌ 更改基类以匹配派生类(应改为更改派生类)
- ❌ 建议在框架代码中使用媒体 API 类型
验证:
- 检查 include:是否包含
#include "base/image/pixel_map.h" - 确认用法:
RefPtr<PixelMap>或RefPtr<OHOS::Ace::PixelMap> - 框架代码中没有
Media::PixelMap引用 - 虚函数返回类型与基类匹配
相关模式:
- 其他包装类型也可能出现类似问题
- 使用媒体类型前,务必检查是否存在 Ace 包装类
- 框架使用 Ace 包装类,接口可能使用媒体类型
2. 构建系统架构
- libace.z.so 仅链接 ace_core_ng 库
- 组件专用库不会链接到最终产物
- libace.z.so 使用的新增 .cpp 文件必须位于 ace_core_ng_source_set 中
2. 符号导出规则 ⭐
⚠️ 关键——非模板类和非模板方法的规则:
遇到非模板类和非模板方法的“未定义符号”链接错误时,请按顺序遵循以下规则:
规则 1:检查类是否具有 ACE_FORCE_EXPORT
首先,检查类定义是否具有 ACE_FORCE_EXPORT:
// Case A: Class WITHOUT ACE_FORCE_EXPORT
class PaddingPropertyF { // ← No export on class
float Width() const;
};
// Case B: Class WITH ACE_FORCE_EXPORT
class ACE_FORCE_EXPORT PaddingPropertyF { // ← Export on entire class
float Width() const;
};
规则 2:根据类的导出状态应用导出规则
情况 A——类没有 ACE_FORCE_EXPORT:
- ✅ 在头文件中的方法声明上添加
ACE_FORCE_EXPORT(首选) - ❌ 不要在 .cpp 中的方法定义上添加
- 原因:将导出控制保留在声明层级,支持精细化导出控制
// ✅ CORRECT - Add to header declaration
// interfaces/inner_api/ace_kit/include/ui/properties/ng/measure_property.h
class PaddingPropertyF {
ACE_FORCE_EXPORT float Width() const; // ← Add here
ACE_FORCE_EXPORT float Height() const; // ← Add here
};
// frameworks/core/components_ng/property/measure_property.cpp
// NO ACE_FORCE_EXPORT here
float PaddingPropertyF::Width() const { /* implementation */ }
情况 B——类具有 ACE_FORCE_EXPORT:
- ✅ 无需额外导出——类导出已涵盖所有方法
- ❌ 不要为单个方法添加
ACE_FORCE_EXPORT - 原因:整个类已经导出
// ✅ CORRECT - No individual method exports needed
class ACE_FORCE_EXPORT PaddingPropertyF { // ← Entire class exported
float Width() const; // Automatically exported
float Height() const; // Automatically exported
};
规则 3:在 libace.map 中使用精细化符号名称添加导出 ⭐
⚠️ 关键:始终优先选择精细化符号导出
应用规则 1 和规则 2 后,将符号添加到 build/libace.map:
// build/libace.map
{
global:
// ✅ FINE-GRAINED - Export specific methods (PREFERRED - ALWAYS TRY THIS FIRST)
OHOS::Ace::PaddingPropertyF::Width*;
OHOS::Ace::PaddingPropertyF::Height*;
// ✅ FINE-GRAINED CONSTRUCTORS - Match all constructor overloads
OHOS::Ace::VelocityTracker::VelocityTracker*; // Matches both constructors
// ⚠️ ACCEPTABLE - Export entire class (ONLY if fine-grained not feasible)
OHOS::Ace::PaddingPropertyF::*;
};
精细化导出模式优先级(按优先顺序排列):
-
方法级(最优先):
OHOS::Ace::ClassName::MethodName*; # Single method -
构造函数级(构造函数):
OHOS::Ace::ClassName::ClassName*; # All constructor overloads -
类级(最后手段):
OHOS::Ace::ClassName::*; # All class members (use sparingly)
为什么要精细化导出? ⭐
- ⭐ 最大限度缩小导出符号范围——仅导出所需内容
- ⭐ 明确 API 边界——清晰说明公共接口
- ⭐ 减少符号冲突——范围越小,冲突越少
- ⭐ 更好地跟踪依赖关系——便于查看跨模块使用情况
- ⭐ 提升安全性——限制可导出的攻击面
- ⭐ 更易维护——明确哪些符号属于 API 契约
决策树:
Linker Error: undefined symbol ClassName::MethodName
↓
Is ClassName a non-template class?
YES → Is class declaration has ACE_FORCE_EXPORT?
YES → ✅ Class already exported
→ Add ClassName::* to libace.map
→ DONE (no individual method exports needed)
NO → ✅ Add ACE_FORCE_EXPORT to method declaration in .h
→ Add ClassName::MethodName* to libace.map
→ DONE
NO → Use template export rules (see below)
对比:非模板与模板
| 方面 | 非模板类/方法 | 模板类/方法 |
|---|---|---|
| 导出位置 | .h 中的方法声明(规则 2) | .h 中的声明和 .cpp 中的定义 |
| 类导出 | 如果存在,则涵盖所有方法 | 单个方法仍需导出 |
| libace.map | 精细化:ClassName::MethodName*; | 通配符:ClassName*; 或 TemplateFunction*; |
| 参考 | 本节(规则 2) | 模式 1,场景 3 和 5 |
⚠️ 仅适用于模板的规则(不适用于非模板):
仅对模板类和模板函数使用不同规则:
模板函数(模式 1,场景 3):
- 在 .h 中的声明和.cpp 中的实现上添加
ACE_FORCE_EXPORT - 在 .cpp 中添加显式实例化
- 在 libace.map 中使用通配符:
void?ClassName::FunctionName*;
模板类方法(模式 1,场景 5):
- 在 .cpp 中的每个方法实现上添加
ACE_FORCE_EXPORT - 仅导出类本身并不足够
- 在 libace.map 中使用通配符:
ClassName*;
不要混用规则:
- ❌ 不要对非模板类使用模板规则
- ❌ 不要对模板类使用非模板规则
3. 头文件优化
- 对仅在头文件中定义的常量使用
inline constexpr - 不要同时在头文件和 .cpp 中定义
- 前向声明可以减少依赖,但要确保使用时类型完整可见
⭐ 关键原则——跨模块使用的符号导出
遇到其他动态库使用的方法出现“未定义符号”链接错误时:
⚠️ 重要:区分非模板和模板
应用任何导出规则之前,首先确定类型:
Linker Error: undefined symbol
↓
Is it a non-template class method?
→ YES: Use Non-Template Rules (below)
→ NO: Use Template Rules (Pattern 1, Scenario 3 & 5)
非模板类方法:在声明处导出 ⭐
✅ 非模板的首选做法——在头文件(.h)中的方法声明处添加 ACE_FORCE_EXPORT:
// interfaces/inner_api/ace_kit/include/ui/properties/ng/measure_property.h
class PaddingPropertyF {
ACE_FORCE_EXPORT float Width() const; // ← Add here (PREFERRED)
ACE_FORCE_EXPORT float Height() const; // ← Add here (PREFERRED)
};
// frameworks/core/components_ng/property/measure_property.cpp
// NO ACE_FORCE_EXPORT here for non-template methods
float PaddingPropertyF::Width() const
{
return left.value_or(0.0f) + right.value_or(0.0f);
}
为什么非模板要采用这种做法?
- 精细控制导出——仅导出所需的方法
- API 边界清晰——从声明中即可看出导出了什么
- 支持 libace.map 的精细规则——导出特定符号
- 与类导出语义一致——如果类已导出,就不需要单独导出各个方法
⚠️ 关键:先检查类的导出状态
// Case A: Class WITHOUT export → Add to individual method declarations
class PaddingPropertyF {
ACE_FORCE_EXPORT float Width() const; // ← Required
};
// Case B: Class WITH export → No individual exports needed
class ACE_FORCE_EXPORT PaddingPropertyF { // ← Entire class exported
float Width() const; // Automatically exported, no ACE_FORCE_EXPORT needed
};
❌ 非模板代码请勿:
- ❌ 在 .cpp 中的方法定义处添加
ACE_FORCE_EXPORT(模板除外) - ❌ 如果类已添加
ACE_FORCE_EXPORT,则不要单独导出各个方法 - ❌ 对非模板代码使用模板导出规则
仅适用于模板:规则有所不同 ⚠️
模板类和模板函数需使用不同规则(参见模式 1、场景 3 和 5):
| 规则类型 | 非模板 | 模板 |
|---|---|---|
| 导出位置 | .h 中的方法声明 | .h 中的声明和 .cpp 中的定义都需要 |
| 类导出是否足够? | 是(涵盖所有方法) | 否(各个方法都需要单独导出) |
| libace.map 模式 | 精细规则:ClassName::MethodName*; | 通配符:ClassName*; |
| 参考 | 本节 | 模式 1、场景 3 和 5 |
各种做法的适用场景
| 场景 | 推荐位置 | 示例 |
|---|---|---|
| 非模板类方法 | .h 中的方法声明 | PaddingPropertyF::Width() |
| 模板类方法 | .h 中的声明和 .cpp 中的实现都需要 | LayoutConstraintT<T>::UpdateMaxSizeWithCheck() |
| 模板函数 | .h 中的声明和 .cpp 中的实现都需要 | StringUtils::StringSplitter<T> |
| 自由函数(非模板) | .h 中的方法声明 | StringUtils::TransformStrCase<std::string> |
| 已添加导出的类 | 无需处理 | 所有方法都会自动导出 |
实际成功案例
-
非模板方法(PaddingPropertyF):
- 在 .h 中的方法声明处添加了
ACE_FORCE_EXPORT - 未修改 .cpp 文件
- libace.map 精细条目:
OHOS::Ace::PaddingPropertyF::Width*;
- 在 .h 中的方法声明处添加了
-
模板类方法(LayoutConstraintT):
- 在 .cpp 中的每个方法实现处添加了
ACE_FORCE_EXPORT - 仅导出类本身对模板并不足够
- libace.map 中的通配符模式:
OHOS::Ace::NG::LayoutConstraintT*;
- 在 .cpp 中的每个方法实现处添加了
-
模板函数(StringUtils):
- 在 .h 中的声明和 .cpp 中的实现处都添加了
ACE_FORCE_EXPORT - 在 .cpp 中显式实例化
- libace.map 中的通配符模式:
void?OHOS::Ace::StringUtils::StringSplitter*;
- 在 .h 中的声明和 .cpp 中的实现处都添加了
请记住:修复其他模块所需符号的链接器错误时,先尝试在 .cpp 实现处修改,再修改头文件。
4. 模板显式实例化 ⭐
对于显式实例化的模板类:
问题:PaddingPropertyT<CalcLength> 符号未导出,但 PaddingPropertyT<Dimension> 可以正常使用
根本原因:当某个类型被用作显式实例化的模板参数时,该类型本身必须带有导出属性
关键原则:
Template Type Visibility (CalcLength)
↓
Template Specialization (PaddingPropertyT<CalcLength>)
↓
Symbol Export Success/Failure
- 如果
CalcLength没有导出属性 → 模板特化会继承内部可见性 - 如果
CalcLength有ACE_FORCE_EXPORT→ 模板特化可以导出
解决方案:
// ❌ BEFORE - Type missing export (fails when used as template parameter)
class CalcLength {
std::string ToString() const { /* inline */ }
};
// ✅ AFTER - Add type-level export (required for template parameters)
class ACE_FORCE_EXPORT CalcLength { // ← Required when used as template type
std::string ToString() const { /* inline - keep in header */ }
};
重要规则:
- 用作模板参数的类型必须导出(如果该类型有内联方法)
- 不要将内联方法移到 .cpp 文件中——保留头文件优化
- 模板方法仍需在 .cpp 文件中使用
ACE_FORCE_EXPORT - 必须在 .cpp 文件中显式实例化
需要类型级导出的情况:
- ✅ 类型有内联方法(ToString、operator== 等)
- ✅ 类型被用作显式实例化的模板参数
- ✅ 模板实例化符号需要导出到其他模块
参考:详细案例研究请参阅 references/template-instantiation-type-export.md
⚠️ 重要——头文件优化:
- StringUtils 重构(提交 e0fd3724d21)将约 650 行代码从头文件移到了 .cpp 文件
- 这是为了缩短构建时间而采取的策略性措施
- 不要通过将内联方法移到 .cpp 文件中来回退此更改
- 使用类型级导出来解决模板参数导出问题
验证步骤
用户应用建议的修复后,建议运行以下验证命令:
# 1. Navigate to OpenHarmony root directory and rebuild
cd <openharmony_root>
./build.sh --product-name rk3568 --build-target ace_engine --ccache
# 2. Check for link errors in out/<product>/build.log
grep "ld.lld: error:" out/rk3568/build.log | wc -l
# 3. Verify symbol export (if applicable)
nm -D out/rk3568/arkui/ace_engine/libace.z.so | grep SymbolName
# 4. Extract and check new errors from out/<product>/build.log
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
# 5. Read the extracted errors:
cat out/rk3568/last_error.log
# For SDK builds (⚠️ use out/sdk/ directory):
# 1. Build SDK: ./build.sh --product-name ohos-sdk --ccache
# 2. Check errors: grep "error:" out/sdk/build.log | tail -50
# 3. Extract errors: foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/sdk/build.log
# 4. Read errors: cat out/sdk/last_error.log
注意:这些命令建议由用户手动运行。不要自动执行构建命令。
添加新案例
解决新的构建错误时:
- 在
examples/<case-name>.md中记录错误 - 包含:
- 错误特征
- 根本原因
- 解决步骤
- 修改的文件
- 验证命令
- 更新此 SKILL.md,引用新案例
- 将相关模式添加到“错误模式与解决方案”部分
使用示例
示例 1:构建成功(没有错误)
User: "帮我分析一下构建错误"
Assistant: "I'll analyze the build errors from the OpenHarmony output directory.
⚠️ FIRST, I'll extract the latest errors from build.log:
[STEP 1 - NAVIGATE TO ROOT]
cd <openharmony_root>
[STEP 2 - EXTRACTION - MANDATORY]
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
Output:
Extracted latest error block (24 bytes) from out/rk3568/build.log
→ Saved to out/rk3568/last_error.log
[STEP 3 - READ EXTRACTED ERRORS]
Reading out/rk3568/last_error.log...
Content:
build success, no error
[STEP 4 - CHECK FOR SUCCESS]
✅ Build is successful! No errors found.
## ✅ 构建成功
最新的构建已经成功完成,没有发现任何错误。
**构建状态**: 成功
**错误信息**: 无
建议: 您可以继续进行开发或测试工作。
[STOP - DO NOT read other log files like error.log]"
示例 2:构建出错
User: "帮我分析一下构建错误"
Assistant: "I'll analyze the build errors from the OpenHarmony output directory.
⚠️ FIRST, I'll extract the latest errors from build.log:
[STEP 1 - NAVIGATE TO ROOT]
cd <openharmony_root>
[STEP 2 - EXTRACTION - MANDATORY]
foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
Output:
Extracted latest error block (12345 bytes) from out/rk3568/build.log
→ Saved to out/rk3568/last_error.log
[STEP 3 - READ EXTRACTED ERRORS]
Reading out/rk3568/last_error.log...
[STEP 4 - CATEGORIZE]
Identified as: compilation error - undeclared identifier
[STEP 5 - MATCH PATTERNS]
Checking references/ directory for similar patterns...
Found match: references/undefined-symbol-missing-cpp.md
[STEP 6 - PROVIDE RECOMMENDATIONS]
Providing detailed fix recommendations (NOT modifying files)
## 构建错误分析
### 错误类型
编译错误 - 未声明的标识符
### 错误位置
frameworks/core/event/touch_event.cpp:25
### 根本原因
函数被删除但调用点未移除:TouchPoint::CovertId() 已删除,但头文件中仍有声明且多处调用
### 修复建议
**选项1: 恢复函数实现** (推荐)
在 frameworks/core/event/touch_event.cpp 中恢复函数:
**当前代码**: (函数已删除)
**应添加**:
```cpp
void TouchPoint::CovertId()
{
if (sourceTool == SourceTool::PEN) {
originalId = TOUCH_TOOL_BASE_ID + static_cast<int32_t>(sourceTool);
id = id + originalId;
}
}
选项2:完全移除函数
需要修改以下文件:
- 删除头文件声明:interfaces/inner_api/ace_kit/include/ui/event/touch_event.h:59
- 移除调用点:adapter/ohos/entrance/mmi_event_convertor.cpp:240
- 移除测试用例:test/unittest/core/event/touch_event_test_ng.cpp:668
验证步骤
- 重新编译:./build.sh --product-name rk3568 --build-target ace_engine
- 重新提取错误:foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
- 检查结果:cat out/rk3568/last_error.log
[NOTE: 仅提供建议,不修改文件]"
**Key Points**:
- ⚠️ **ALWAYS navigate to root first** → `cd <openharmony_root>` before running extraction script
- ⚠️ **ALWAYS extract first** → use script on `out/<product>/build.log` with correct relative path from root
- ✅ **THEN read** → analyze only from `out/<product>/last_error.log`
- ❌ **NEVER read** → `build.log` directly or other log files
- ✅ **Check for success** → if `last_error.log` shows "build success", STOP and report
- ❌ **NEVER read other logs** → DO NOT read `error.log`, `build_output*.log`, `error.*.log`, etc.
- ✅ **Use correct script path** → `foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/` (from OpenHarmony root)
- ✅ **Provide recommendations ONLY** → do NOT automatically modify code
## Version History
- **0.14.0** (2026-02-10): Non-Template Symbol Export Rules
- ⚠️ **重要修改**:区分非模板类和模板类的符号导出规则
- 📋 **非模板类规则**:优先在方法声明处(.h 文件)增加 ACE_FORCE_EXPORT
- 🔍 **类导出检查**:如果 class 已有 ACE_FORCE_EXPORT,则无需额外导出方法
- 🎯 **精细化导出**:libace.map 中优先使用细粒度符号名(ClassName::MethodName*)
- 📊 **决策树**:添加非模板 vs 模板的决策流程图
- 🔧 **对比表格**:非模板与模板导出规则的清晰对比
- ✅ **更新位置**:
- Best Practices 部分:"### 2. Symbol Export Rules ⭐"
- Key Principles 部分:"## ⭐ Key Principles - Symbol Export for Cross-Module Usage"
- Pattern 1 Scenario 2:更新为基于类导出状态的决策流程
- 🚫 **禁止事项**:非模板方法不要在 .cpp 中添加 ACE_FORCE_EXPORT
- 💡 **核心理念**:非模板支持精细化导出控制,模板需要宽泛通配符
- **0.13.0** (2026-02-10): CRITICAL RULE - Never Clear LTO Cache
- ⚠️ **关键修改**:绝对禁止建议清理 LTO cache 或删除构建目录
- 📋 在 Behavior Guidelines 部分添加 "CRITICAL - NEVER SUGGEST CLEARING LTO CACHE" 子部分
- 🚨 在 Best Practices 部分添加全新的 "CRITICAL RULE - NEVER Clear LTO Cache" 高优先级规则
- 💡 强调 LTO cache 问题极其罕见(99.9% 的情况都不是缓存问题)
- ⏱️ 说明清理缓存会浪费大量时间(完整重建需要数小时,增量编译只需数分钟)
- 🎯 明确要求必须分析真实错误并修复根本原因,而不是怪罪缓存
- 📝 加强 Pattern 1 Common mistakes 部分的相关说明
- ⛔ 仅在极端罕见情况下(用户明确修改了编译器标志等)才考虑缓存问题
- ✅ 即使在那些情况下,也必须先询问用户并记录具体原因
- **0.12.0** (2026-02-10): Case 11 - std::function<RefPtr<T>> Forward Declaration
- ✨ 新增 Case 11:std::function<RefPtr<T>> 前向声明成功案例
- 📝 新增案例分析文档:`case-std-function-refptr-forward-declaration.md`
- 🎯 核心观点:using 别名不立即实例化模板,RefPtr<T> 大小固定
- 💡 关键发现:模板实例化延迟到 .cpp 实际使用时
- 📊 对比表格:Case 6/8/11 的 RefPtr<T> 前向声明使用场景
- 🔧 纠正错误理解:std::function 不需要完整类型定义
- ✅ 实战案例:OpenHarmony gesture_event_hub.h 优化成功应用
- 🛡️ 验证原则:RefPtr<> 本质是指针包装器,前向声明可行
- **0.11.0** (2026-02-09): High Priority Rule - Header Optimization Protection
- ⭐ 新增高优先级规则:处理头文件缺失错误时的优先级顺序
- 🔍 新增优化检测方法:通过 git status 检测暂存/修改/最近提交状态
- ✅ 优先方案:在出错的 .cpp 文件中添加缺失头文件(推荐)
- ⚠️ 最后手段:仅在用户确认后才修改已优化的头文件
- 📝 新增用户确认模板:清晰展示两个方案供用户选择
- 🔧 新增检测工作流:三步检测并应用正确的解决方案
- 🛡️ 保护头文件优化成果:避免盲目回退已删除的依赖
- 🔗 关联 header-optimization 和 compile-analysis skills
- **0.10.0** (2026-02-06): Case 8 Advanced Scenario - Struct with Own .cpp File
- ✨ 新增 Case 8 进阶场景:当辅助方法模式不够时的完整解决方案
- 📝 在 `forward-declaration-struct-helper-method.md` 中添加 Advanced Scenario
- 🎯 应用场景:结构体有 .cpp 文件,被 std::vector 使用,需要保持聚合初始化
- 💡 关键技术:升级为完整的前向声明优化(类似 Case 6),同时保留辅助方法
- 🔧 决策树:指导何时使用标准方案 vs 完整方案
- 📊 对比表格:标准方案(无 .cpp)vs Advanced Scenario(有 .cpp)
- ⚠️ 渐进式升级:从辅助方法模式开始,遇到 vector 拷贝问题时升级
- 🔗 关联 Case 6 和 Case 7:完整的 RefPtr 成员优化知识体系
- **0.9.0** (2026-02-04): LTO Virtual Thunk Export Solution
- ✨ 新增 LTO Virtual Thunk 导出案例分析:`lto-virtual-thunk-libace-map-export.md`
- 📝 新增 Case 9:基类虚函数 + LTO 的 libace.map 导出解决方案
- 🎯 核心观点:保持前向声明优化,不要回退到 inline 定义
- 💡 关键技术:在 libace.map 中同时导出 `ClassName::*;` 和 `virtual?thunk?to::ClassName::*;`
- 🔧 LTO 工作原理:链接时创建 virtual thunk 调整 this 指针
- 📊 符号导出模式:通配符 `*` 匹配所有方法和 virtual thunk
- ⚠️ 常见错误:只导出类符号而忘记导出 virtual thunk
- **0.8.0** (2026-02-04): Struct RefPtr Member Optimization
- ✨ 新增结构体智能指针成员优化案例分析:`forward-declaration-struct-helper-method.md`
- 📝 新增 Case 8:纯数据结构中 RefPtr 成员的辅助方法模式
- 🎯 与 Case 6 的区别:Case 6 适用于有自己 .cpp 的类,Case 8 适用于纯数据结构
- 💡 关键技术:使用辅助方法封装完整类型访问,保持头文件前向声明优化
- 🔧 完全限定名 `::OHOS::Media::PixelMap` 避免命名空间混淆
- 📊 编译性能优化:减少头文件依赖,降低重编译范围
- **0.7.0** (2026-02-02): Test Linking Error Support
- ✨ 新增测试链接错误案例分析:`test-missing-source-files.md`
- 📝 新增 Case 7:测试中缺失源文件的通用解决方案
- 🎯 泛化案例场景,不仅限于 LogWrapper,涵盖所有缺失源文件情况
- 📋 列出常见缺失源文件:log_wrapper.cpp、string_utils.cpp、layout_constraint.cpp 等
- ⚠️ 强调只添加源文件,不修改 cflags/configs/defines
- 🌳 添加决策树帮助判断正确的解决方案
- **0.6.0** (2025-02-02): 新增 SDK 编译错误分析支持
- ✨ 添加 SDK 错误触发关键词:"分析SDK的编译错误"、"analyze SDK build errors"、"check SDK errors"
- ⚠️ 特别说明:SDK 日志路径为 `out/sdk/build.log` 和 `out/sdk/last_error.log`(特殊目录)
- 📝 更新所有路径示例,标注 SDK 的特殊目录结构
- 🔧 优化错误提取命令,支持 SDK 路径
- **0.5.0** (2026-01-27): 模板类方法导出支持
- **0.4.0** (2026-01-27): 模板实例化类型导出支持
- **0.3.0** (2026-01-27): 符号导出最佳实践
- **0.2.0** (2026-01-27): 添加重定义错误案例
- **0.1.0** (2026-01-27): 初始版本,包含未定义符号案例研究
## Related Skills
- **openharmony-build**: For building OpenHarmony codebase
- **compile-analysis**: For analyzing compilation performance and dependencies