改了 C++ 代码却没生效?OpenHarmony 原生模块的五步排查法
修改了一段 C++ 代码,编译没有报错,设备上的行为却没有变化。遇到这种情况,很容易先怀疑缓存,再重新编译整个系统。但如果改动没有进入当前产品的构建链路,重复编译只会再次得到相同结果。
阅读 Laval 社区的编译构建、clangd 配置和产物命名文章后,我把这个问题整理成五个连续的检查点:改的是哪份源码,文件属于哪个目标,实际使用什么编译参数,更新了哪个产物,运行时是否执行到新代码。
本文面向使用 GN/Ninja 构建的 OpenHarmony 原生 C/C++ 模块。所有名称和路径均为独立教学占位,不对应实际业务工程;示例用于说明排查方法,不代表已在某款开发板上完成验证。不同版本、系统类型和产品的构建入口,以对应源码树为准。
先把“没生效”拆成五个问题
可以按下面这条链逐段检查:
修改的源码
↓ 是否属于本次构建使用的源码树?
当前产品的构建目标
↓ 是否包含这个源文件?
编译命令与链接输入
↓ 是否使用了预期宏、头文件和目标架构?
构建产物与部署文件
↓ 是否对应同一次构建?
运行中的程序
↓ 是否加载新文件,并触发修改后的分支?
可观察的行为变化
这条链上的每一步都需要自己的证据。编译成功只能回答其中一部分问题。
第一步:确认修改与构建指向同一份源码
假设要排查的文件是 examples/build_probe/build_probe.cpp。在多套源码、多个工作区或远程开发环境中,先核对编辑器打开的路径与构建终端所在路径。
在该文件所属的 Git 仓库中执行:
pwd
git rev-parse --show-toplevel
git branch --show-current
git rev-parse HEAD
git status --short
git diff -- build_probe.cpp
最后一条命令的路径应替换为文件相对当前目录的实际路径。OpenHarmony 常通过 repo 管理多个 Git 仓库,不能假定源码总目录本身就是要检查的 Git 仓库;如果处于 detached HEAD,分支名可能为空,此时结合提交号判断。
同时记下这次构建的产品名称和输出目录。文件名相同、编辑器能跳转到定义,都不足以证明构建使用了正在修改的那份文件。
第二步:确认源文件进入了目标
OpenHarmony 的产品配置、部件配置与模块目标共同决定构建范围。可以先沿“产品是否选择部件 → 部件是否接入模块 → 模块是否包含源文件”的顺序检查。相关概念可对照 OpenHarmony 编译构建仓库说明。
下面用 out/demo_product 表示已经生成有效构建图的输出目录,用 //examples/build_probe:build_probe 表示教学目标标签。执行命令前,应替换为工程中的真实值,并使用该源码树配套的 GN/Ninja 工具。
# 在 OpenHarmony 源码根目录执行
gn ls out/demo_product '//examples/build_probe:*'
gn desc out/demo_product //examples/build_probe:build_probe sources
gn desc out/demo_product //examples/build_probe:build_probe deps --tree
先确认目标存在,再检查 sources 是否包含被修改的文件,以及依赖是否符合预期。目标存在但没有该文件时,需要回到对应的 BUILD.gn 和条件分支寻找原因。gn desc 查询的是指定输出目录对应的目标信息,命令含义见 GN 官方参考。
如果刚调整过产品配置或构建配置,应先通过该版本支持的构建入口重新生成构建图。也要注意工具链后缀:一个模块可能同时存在面向宿主机和设备的目标,检查时应选中本次构建实际使用的那个。
第三步:检查真正交给编译器的参数
源文件进入目标后,再看它怎样被编译。重点检查三个方面:
- 宏定义是否启用了预期分支。
- 头文件搜索路径是否指向预期版本。
- 编译器及目标架构是否属于设备侧构建。
gn desc out/demo_product //examples/build_probe:build_probe defines --blame
gn desc out/demo_product //examples/build_probe:build_probe include_dirs --blame
--blame 可以帮助追溯配置来源,避免只改了一处 defines,却忽略其它配置带来的影响。具体支持情况可用源码树配套工具的 gn help desc 核对。
随后查看生成的 Ninja 命令:
# 先找出真实的 Ninja 目标或产物路径
ninja -C out/demo_product -t targets all
# 将下面的占位值替换为上一步确认的目标
ninja -C out/demo_product -t commands '<实际 Ninja 目标或产物路径>'
GN 标签与 Ninja 目标名不一定能直接互换。-t commands 展示的是构建图中的命令,可以包含依赖目标的命令,它本身不会执行编译,也不能证明某条命令本轮执行过。要确认本次是否重新编译,应查看本轮构建日志,必要时在受控的模块构建中打开详细命令输出。相关行为见 Ninja 官方手册。
如果已配置 clangd,还可以对照 compile_commands.json 中该文件的 directory、file 和 arguments 或 command。这些字段分别说明编译工作目录、源文件和实际参数;同一文件也可能对应多条编译记录,不能只看第一条。格式定义见 Clang 编译数据库说明。
编译数据库是生成时的记录。切换产品、工具链或宏配置后,需要按当前工程的方式重新生成,并确认编辑器读取的是新文件。
第四步:沿产物去向核对部署文件
gn desc out/demo_product //examples/build_probe:build_probe outputs
该命令用于查询目标的输出信息。对于聚合或包装目标,结果可能是中间文件,还需要继续查看实际编译、链接目标以及产品打包配置。
目标名、产物文件名、设备安装路径是三个不同的信息。 GN 的输出配置和 OpenHarmony 构建模板可以影响产物名称,不能只按目标名猜一个 .so 文件,然后认定它就是要部署的文件。
核对时可以建立一张小表:
| 环节 | 应确认的内容 |
|---|---|
| 编译、链接输出 | 本轮目标实际生成的文件与日志 |
| 打包输入 | 产品打包使用的文件是否来自本轮输出 |
| 最终安装文件 | 镜像或部署流程最终放入设备的文件 |
| 运行时使用 | 进程实际执行或加载的文件是否对应本次部署 |
比较文件时,大小和时间戳适合初步筛选;如果两端本应是完全相同的文件,可以再比较哈希。比较前先确认中间是否经过 strip、签名或其它处理,不能把处理前后的不同哈希直接判成部署错误。
同时检查同名旧文件、预编译替代项和部署目标设备。某个输出目录中出现了新文件,并不能单独证明最终镜像已经包含它。
第五步:用明确的触发条件验证运行行为
最后回到一个可观察的变化。下面的独立 C++ 程序可以用来理解“标记—触发—结果”之间的关系:
#include <cstdio>
int main()
{
std::puts("BUILD_PROBE_V2");
return 0;
}
这个程序的预期行为很明确:执行当前版本时,标准输出出现 BUILD_PROBE_V2。它只演示验证标记,不包含 OpenHarmony 产品接入配置。
在实际模块中,应选一个已经明确的触发点,例如测试入口或一次可重复的操作,再使用模块现有的日志机制记录无业务数据的标记。服务进程的标准输出未必就是平时查看的系统日志,不能仅凭某个日志窗口没有字符串判断代码没有运行。
部署动态库后,还应按产品支持的流程重启对应进程或设备,确认新文件被重新加载。然后记录本次触发条件、观察结果和必要的回归检查。临时标记完成定位后及时移除。
怎样描述排查结果才准确
同样是“检查过了”,不同证据支持的结论并不一样:
| 已获得的证据 | 可以给出的结论 |
|---|---|
目标存在,源文件在 sources 中 | 文件进入了该配置的构建目标 |
| 本轮编译、链接成功,并确认目标产物 | 完成了构建验证 |
| 核对了最终打包、部署文件 | 完成了产物交付检查 |
| 新进程执行后出现预期变化 | 该触发场景完成了运行验证 |
某个场景运行正常,仍需要按改动范围检查异常分支和相关场景。这样记录结果,下一次排查时就能从尚未确认的环节继续,而不是重新猜测整条链路。
遇到“改了代码却没生效”,我更建议先回答一个具体问题:当前还缺哪一段证据? 找到这段缺口,下一条命令通常也就明确了。
阅读来源
本文参考以下社区文章的选题与讲解方向,排查步骤为重新组织的教学说明,没有复用原文代码或截图:
- xiaodong:OpenHarmony源码学习之编译构建。
- 离北况归:OpenHarmony开发环境配置——使用clangd。
- 离北况归:OpenHarmony编译构建中如何指定产物名称和拓展名。
工具行为以正文链接的 GN、Ninja、Clang 官方文档及所用版本的本地帮助为准。
更多推荐
所有评论(0)