mcpField
B
build-error-analyzer

build-error-analyzer

github.com/openharmonyinsight开发3589 次安装11 周前更新
代码仓库

关于 build-error-analyzer

build-error-analyzer 是面向 OpenHarmony 开发者的构建错误分析技能。它先从 build.log 提取最新错误到 last_error.log,再结合已知案例提供修复建议,但不会自动修改代码。

主要功能

  1. 01提取最新构建错误块
  2. 02仅分析 last_error.log
  3. 03按类别分析编译与链接错误
  4. 04匹配历史案例和错误模式
  5. 05提供根因、修复建议及验证步骤

适用场景

  1. 01定位 OpenHarmony 最新构建错误
  2. 02分析 SDK 构建失败
  3. 03为链接错误查找可能原因
  4. 04获取人工应用的修复建议

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/openharmonyinsight/openharmony-skills --skill build-error-analyzer

安装后在 Claude Code 中输入/build-error-analyzer确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
examples/
USAGE_EXAMPLES.md
references/
build-system-ace-core-ng-source-set.md
case-std-function-refptr-forward-declaration.md
forward-declaration-refptr-member.md
forward-declaration-struct-helper-method.md
lto-virtual-thunk-libace-map-export.md
mingw-dllexport-declaration-mismatch.md
namespace-ambiguous-type-resolution.md
redefinition-error-constexpr.md
symbol-export-ace-force-export.md
symbol-export-libace-map.md
template-instantiation-type-export.md
test-missing-source-files.md
undefined-symbol-missing-cpp.md
script/
extract_last_error.sh

详细介绍

构建错误分析技能

此技能专门用于分析 OpenHarmony 的 last_error.log 中的构建错误,并根据错误模式和历史案例提供修复建议(仅提供建议,不会自动修改代码)。

重要:此技能仅提供分析和建议,不会自动修改代码文件。

⚠️ 关键工作流(必须遵循)

必须始终严格遵循以下顺序:

  1. 首先:从 out/<product>/build.log 提取错误 → 生成 out/<product>/last_error.log
  2. 然后:仅从 out/<product>/last_error.log 读取并分析
  3. 绝不:直接读取 out/<product>/build.log 或任何其他日志文件(error.log、build_output*.log 等)
  4. 成功情况:如果 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 error
  • no 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”

工作流:

  1. ✅ 使用脚本从 out/<product>/build.log 提取错误 → 创建 out/<product>/last_error.log
  2. ✅ 对于 SDK:从 out/sdk/build.log 提取 → 创建 out/sdk/last_error.log
  3. ✅ 仅分析提取后的 last_error.log 中的错误
  4. ✅ 仅提供修复建议——不要修改代码

应避免的常见错误:

  • ❌ 直接读取 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 缓存问题极为少见,将清除缓存作为首选方案会掩盖真正的问题、浪费重建时间,并影响增量编译的优势。

前提条件

  1. ⚠️ 关键事项:读取任何内容前,务必先从 out/<product>/build.log 提取错误
  2. 错误日志位置:此技能仅从 out/<product>/last_error.log(与 build.log 位于同一目录)读取错误
  3. 错误提取:读取前,务必使用 foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh 中的提取脚本(从 OpenHarmony 根目录运行),生成或更新 last_error.log
  4. 源 build.log:错误仅从 OpenHarmony 输出目录中的 out/<product>/build.log 提取
  5. 代码库上下文:可从任意 OpenHarmony 目录(ace_engine、根目录等)执行
  6. ⚠️ 成功检查:如果 last_error.log 包含“build success”或“no error”,请立即停止并报告成功——不要读取 error.log、build_output*.log 等其他日志文件
  7. 唯一日志来源:仅从 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 error
  • no error
  • 或类似的成功消息

立即停止并报告:

## ✅ 构建成功

最新的构建已经成功完成,没有发现任何错误。

**构建状态**: 成功
**错误信息**: 无

建议: 您可以继续进行开发或测试工作。

不要继续:

  • ❌ 在 error.log 中搜索错误
  • ❌ 在 build_output*.log 中搜索错误
  • ❌ 在 error.*.log 文件中搜索错误
  • ❌ 查找历史错误
  • ✅ 停止并报告成功

只有在存在实际错误时,才继续进行分类:

错误类别:

  1. 编译错误(CXX 任务)- 语法错误、缺少头文件、类型不完整
  2. 链接器错误(SOLINK/LINK 任务)- 未定义符号、缺少库
  3. 构建系统错误 - GN/Ninja 配置问题
步骤 3:与已知案例匹配

将错误模式与 references/ 目录中的历史案例进行比较,并提供具体解决方案。

案例匹配优先级:

  1. 按错误特征匹配(例如,“undefined symbol” + “referred by”)
  2. 按错误上下文匹配(例如,“timepicker module” 的用法)
  3. 按文件模式匹配(例如 BUILD.gn 问题)
  4. 如果没有匹配的具体案例,则提供一般性指导
步骤 4:提供修复建议(不自动修改)

严重:此步骤仅提供建议。不要使用 Edit/Write 工具修改代码。

根据错误类型和匹配到的案例,提供:

  • 根本原因分析
  • 需要修改的具体文件(包含准确的行号和代码片段)
  • 构建系统配置更改(包含准确的 GN 文件路径)
  • 验证步骤

建议的输出格式:

## 构建错误分析

### 错误类型
[编译错误/链接错误/构建系统错误]

### 错误位置
[文件路径:行号]

### 错误信息
[完整的错误消息]

### 根本原因
[详细解释为什么会出现这个错误]

### 修复建议

**文件**: [完整文件路径:行号]

**当前代码**:
```cpp
[错误代码片段]

应修改为:

[正确代码片段]

修改说明:[解释为什么要这样修改]

验证步骤
  1. [第一步验证操作]
  2. [第二步验证操作]
  3. [如何确认修复成功]
参考

[相关的历史案例或文档链接]


**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/
  1. 如果实现已存在,请检查 BUILD.gn:

    grep -r "implementation_file.cpp" frameworks/*/BUILD.gn
    
  2. 将 .cpp 添加到 BUILD.gn:

    • 查找同一目录中其他文件的添加位置
    • 将缺少的 .cpp 添加到合适的 source_set
    • 重要:参考同一目录中的文件,以确定正确的 source_set
    • 不要假设所有文件都应添加到 ace_core_ng_source_set
  3. 通过其他文件验证:

    # 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.so
    • libace_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();

常见原因:

  1. 缺少头文件包含
  2. 只有前向声明,没有完整定义
  3. RefPtr 作为类成员,但未进行适当的实现分离
  4. 头文件优化问题

⚠️ 特殊情况: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'

常见原因:

  1. 多个翻译单元中存在多个定义
  2. 仅在头文件中定义,却缺少 inline/constexpr
  3. 违反 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

正确解决方案:不要撤销前向声明!使用实现分离:

  1. 在头文件中保留前向声明:class Animator;
  2. 在头文件中声明特殊成员函数(不带 = default)
  3. 在 .cpp 文件中使用 = default 实现这些函数
  4. 在 .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_
  • 纯数据结构没有自己的实现文件

解决方案:添加辅助方法来封装类型访问:

  1. 在结构体中声明辅助方法:std::shared_ptr<::OHOS::Media::PixelMap> GetPixelMapSharedPtr() const;
  2. 新建实现文件(例如 interaction_data.cpp)
  3. 在 .cpp 文件中实现辅助方法,并包含完整的头文件:#include "base/image/pixel_map.h"
  4. 将用法更新为调用辅助方法,而不是直接使用 -> 访问
  5. 将新的 .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)
  • 删除命名空间前缀会导致编译器在错误的命名空间中搜索
  • 在错误的命名空间中进行前向声明没有帮助

正确的解决方案(按优先级排序):

  1. 保留完整的命名空间限定符(推荐):

    // In OHOS::Ace::NG namespace
    using OnDragStartFunc = std::function<DragDropBaseInfo(
        const RefPtr<OHOS::Ace::DragEvent>&,  // ✅ Keep full namespace
        const std::string&)>;
    
  2. 在正确的命名空间中添加前向声明:

    // 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&)>;
    
  3. 添加头文件依赖(需要完整类型时):

    #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 导出项:

  1. 将析构函数保留在 .cpp 文件中:头文件中使用 ~TouchEventTarget() override;,实现在 .cpp 文件中
  2. 添加到 libace.map:
    • OHOS::Ace::TouchEventTarget::*; - 导出该类的所有符号
    • virtual?thunk?to?OHOS::Ace::TouchEventTarget::*; - 导出 LTO 虚拟 thunk
  3. 不要改回在头文件中内联定义

要点:

  • 需要两种导出模式:ClassName::*; 和 virtual?thunk?to::ClassName::*;
  • 通配符 * 会匹配包括虚拟 thunk 在内的所有方法
  • 保留前向声明优化
  • 无需为了 LTO 而牺牲头文件优化

为什么有效:

  1. 编译阶段:头文件使用前向声明,.cpp 文件中包含实现
  2. LTO 链接阶段:LTO 优化虚函数表并创建虚拟 thunk
  3. 符号导出:libace.map 中的模式匹配虚拟 thunk,并将其导出到动态符号表
  4. 运行时:派生类虚函数表可以正确引用虚拟 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(规则不同)
  • ❌ 链接错误(这是编译错误)

预防检查清单:

  1. ✅ 确认是否需要导出(跨模块使用)
  2. ✅ 为所有头文件声明添加 ACE_FORCE_EXPORT
  3. ✅ 为所有 .cpp 定义添加 ACE_FORCE_EXPORT
  4. ✅ 检查所有重载版本(不要遗漏)
  5. ✅ 在 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 文件
  1. 创建实现文件(.cpp)
  2. 将其添加到组件的 BUILD.gn
  3. 如果 libace.z.so 使用该文件,则将其添加到 frameworks/core/BUILD.gn ace_core_ng_source_set
  4. 如果符号被跨模块使用:
    • 在头文件声明中添加 ACE_FORCE_EXPORT
    • 将其添加到 build/libace.map 白名单
对于头文件优化问题
  1. 检查是否错误地使用了前向声明
  2. 确保在需要的位置提供完整类型
  3. 对仅限头文件的常量使用 inline constexpr
  4. 避免 ODR 违规
对于链接错误
  1. 从错误消息中确定缺失的符号
  2. 查找符号定义的位置
  3. 验证实现是否已编译(在 BUILD.gn 中)
  4. 如果跨模块使用,检查符号可见性/导出
  5. 验证链接顺序和依赖项

最佳实践

⚠️ 关键规则 - 绝不要清除 LTO 缓存或建议“干净构建”

⚠️ 绝对禁止 - 遇到任何构建错误时:

规则说明: 在任何情况下,都绝不要把清除 LTO 缓存、删除构建目录或执行“干净构建”作为首选或主要解决方案。

严格禁止的做法:

  • ❌ 绝不要建议清除 thinlto-cache 目录
  • ❌ 绝不要建议清除 llvmcache-* 目录
  • ❌ 绝不要建议删除 out/obj 目录
  • ❌ 绝不要建议删除整个 out/ 目录
  • ❌ 绝不要建议运行 rm -rf out/
  • ❌ 绝不要建议“干净构建”或“从头重新构建”
  • ❌ 绝不要把链接器错误归咎于 LTO 缓存
  • ❌ 绝不要说“可能是缓存问题,试着清除缓存”

禁止这样做的原因:

  1. LTO 缓存问题极其罕见 - 99.9% 的构建错误都是代码或配置问题,而不是缓存问题
  2. 掩盖真正的问题 - 清除缓存只能暂时隐藏问题,无法修复根本原因
  3. 浪费大量时间 - 完整重建需要数小时;增量构建只需数分钟
  4. 失去增量编译的优势 - 后续构建会变慢
  5. 破坏可复现性 - 破坏构建状态,让调试更困难
  6. 诊断不充分 - 建议清除缓存说明没有充分分析错误

请记住:真正的构建错误都有真正的原因。找到原因并修复。不要归咎于缓存。


0. ⭐ 高优先级规则 — 解决头文件缺失错误

⚠️ 关键规则 — 遇到“找不到文件”或“没有此文件或目录”的头文件错误时:

规则说明: 如果编译因头文件缺失而失败,始终按以下优先顺序处理:

  1. 首选方案:将缺失的头文件添加到发生错误的 .cpp 文件中(而不是经过优化的头文件)
  2. 最后手段:只有在获得用户确认后,才考虑修改经过优化的头文件

为什么要遵守此规则?:

  • 头文件优化工作会有意移除依赖项,以提升构建速度
  • 盲目地重新添加已删除的依赖项会破坏优化效果
  • .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 文件中
  • 如果头文件未经优化 → 可以添加到头文件中
  • 如果无法确定 → 向用户说明两种方案并征求意见

验证步骤:

  1. 检查 git 状态,查看是否有已暂存/已修改的头文件
  2. 检查最近一次提交中是否有头文件变更
  3. 找出发生编译错误的 .cpp 文件
  4. 将缺失的 #include 添加到该 .cpp 文件中(首选)
  5. 验证修复是否解决了错误
  6. 如果有必要修改头文件,请记录原因

关键原则:

  • ✅ 保留头文件优化工作
  • ✅ 将缺失的包含项添加到 .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

解决方案模式:

  1. 确定错误位置:

    • 检查文件是否位于 frameworks/ 目录中
    • 确认是否使用了框架代码模式
  2. 使用正确的类型:

    // ✅ 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
    
  3. 修复所有出现位置:

    # 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 类型

验证:

  1. 检查 include:是否包含 #include "base/image/pixel_map.h"
  2. 确认用法:RefPtr<PixelMap> 或 RefPtr<OHOS::Ace::PixelMap>
  3. 框架代码中没有 Media::PixelMap 引用
  4. 虚函数返回类型与基类匹配

相关模式:

  • 其他包装类型也可能出现类似问题
  • 使用媒体类型前,务必检查是否存在 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::*;
};

精细化导出模式优先级(按优先顺序排列):

  1. 方法级(最优先):

    OHOS::Ace::ClassName::MethodName*;  # Single method
    
  2. 构造函数级(构造函数):

    OHOS::Ace::ClassName::ClassName*;  # All constructor overloads
    
  3. 类级(最后手段):

    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);
}

为什么非模板要采用这种做法?

  1. 精细控制导出——仅导出所需的方法
  2. API 边界清晰——从声明中即可看出导出了什么
  3. 支持 libace.map 的精细规则——导出特定符号
  4. 与类导出语义一致——如果类已导出,就不需要单独导出各个方法

⚠️ 关键:先检查类的导出状态

// 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>
已添加导出的类无需处理所有方法都会自动导出
实际成功案例
  1. 非模板方法(PaddingPropertyF):

    • 在 .h 中的方法声明处添加了 ACE_FORCE_EXPORT
    • 未修改 .cpp 文件
    • libace.map 精细条目:OHOS::Ace::PaddingPropertyF::Width*;
  2. 模板类方法(LayoutConstraintT):

    • 在 .cpp 中的每个方法实现处添加了 ACE_FORCE_EXPORT
    • 仅导出类本身对模板并不足够
    • libace.map 中的通配符模式:OHOS::Ace::NG::LayoutConstraintT*;
  3. 模板函数(StringUtils):

    • 在 .h 中的声明和 .cpp 中的实现处都添加了 ACE_FORCE_EXPORT
    • 在 .cpp 中显式实例化
    • libace.map 中的通配符模式:void?OHOS::Ace::StringUtils::StringSplitter*;

请记住:修复其他模块所需符号的链接器错误时,先尝试在 .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 */ }
};

重要规则:

  1. 用作模板参数的类型必须导出(如果该类型有内联方法)
  2. 不要将内联方法移到 .cpp 文件中——保留头文件优化
  3. 模板方法仍需在 .cpp 文件中使用 ACE_FORCE_EXPORT
  4. 必须在 .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

注意:这些命令建议由用户手动运行。不要自动执行构建命令。

添加新案例

解决新的构建错误时:

  1. 在 examples/<case-name>.md 中记录错误
  2. 包含:
    • 错误特征
    • 根本原因
    • 解决步骤
    • 修改的文件
    • 验证命令
  3. 更新此 SKILL.md,引用新案例
  4. 将相关模式添加到“错误模式与解决方案”部分

使用示例

示例 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:完全移除函数

需要修改以下文件:

  1. 删除头文件声明:interfaces/inner_api/ace_kit/include/ui/event/touch_event.h:59
  2. 移除调用点:adapter/ohos/entrance/mmi_event_convertor.cpp:240
  3. 移除测试用例:test/unittest/core/event/touch_event_test_ng.cpp:668
验证步骤
  1. 重新编译:./build.sh --product-name rk3568 --build-target ace_engine
  2. 重新提取错误:foundation/arkui/ace_engine/.claude/skills/build-error-analyzer/script/extract_last_error.sh out/rk3568/build.log
  3. 检查结果: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

评论

支持 Markdown