修改了一段 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 中该文件的 directoryfileargumentscommand。这些字段分别说明编译工作目录、源文件和实际参数;同一文件也可能对应多条编译记录,不能只看第一条。格式定义见 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文件进入了该配置的构建目标
本轮编译、链接成功,并确认目标产物完成了构建验证
核对了最终打包、部署文件完成了产物交付检查
新进程执行后出现预期变化该触发场景完成了运行验证

某个场景运行正常,仍需要按改动范围检查异常分支和相关场景。这样记录结果,下一次排查时就能从尚未确认的环节继续,而不是重新猜测整条链路。

遇到“改了代码却没生效”,我更建议先回答一个具体问题:当前还缺哪一段证据? 找到这段缺口,下一条命令通常也就明确了。

阅读来源

本文参考以下社区文章的选题与讲解方向,排查步骤为重新组织的教学说明,没有复用原文代码或截图:

工具行为以正文链接的 GN、Ninja、Clang 官方文档及所用版本的本地帮助为准。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐