OpenHarmony全量编译与打包实战:从RK3568设备树到系统镜像
2026/9/6 8:56:38 网站建设 项目流程

第一次接触OpenHarmony的全量编译与打包,说实话,我当时的感受是既兴奋又忐忑。兴奋的是,从源码开始构建一个完整的操作系统镜像,这种从零到一的过程对开发者来说有着天然的吸引力;忐忑的是,这套系统的编译链路涉及工具链、内核、HDF驱动、子系统、应用框架等多个层面,任何一个环节出了岔子,排查起来都相当费力。这篇文章我把自己的首次全量编译与打包经历完整记录下来,把其中涉及的核心概念、设备树如何选择、编译参数怎么配、打包产物怎么用、常见坑怎么避,一次性讲清楚。无论你是刚接触OpenHarmony的新手,还是已经在做应用开发想往系统层面深入的老手,这篇内容应该都能给你提供一份可以直接照做的参考。

1. 全量编译到底在做什么

1.1 它不是一次简单的代码构建

很多从应用开发转过来的朋友,第一次听说"全量编译"的时候,会觉得它跟Android的make或者gradle build差不多,点一下按钮等结果就行。但实际上,OpenHarmony的全量编译复杂程度要高得多。它不仅仅是把C/C++代码编译成二进制,而是要把整个操作系统的所有组成部分全部构建出来,包括内核镜像、根文件系统、HDF驱动框架、系统服务、关键应用,以及最终的可烧录镜像包。

我打个比方。如果说应用开发是盖房子里的"装修",那全量编译就是"从烧砖开始把整栋楼盖起来"。装修只需要在已有的结构上做文章,而全量编译要处理的,是从最底层的地基(内核、硬件抽象层)到最上层的软装(系统应用、UI框架)的所有环节。这个过程会调用到数十万个编译任务,涉及的工具链包括Clang/GCC交叉编译器、Ninja构建系统、GN元构建系统、Python脚本、Node.js/hvigor构建工具等,它们相互配合,缺一不可。

我第一次跑全量编译的时候,光看编译日志就花了不少时间。日志里既有C/C++的编译告警,也有JavaScript/TypeScript的打包信息,还有各种镜像生成工具的提示。这种多语言、多工具链混编的复杂度,是OpenHarmony全量编译跟普通单语言项目最大的区别。

1.2 为什么要强调"全量"两个字

在实际开发中,我们通常会区分增量编译和全量编译。增量编译只重新构建发生过变更的模块,速度很快,适合日常迭代。比如你只是改了一个HDF驱动的一个.c文件,增量编译可能几分钟就完成了。

但全量编译的意义在于,它提供了一条"干净、确定、可复现"的构建路径。当你遇到以下情况时,就必须做全量编译:

  • 切换了版本分支或者拉取了最新的主干代码,代码基线发生重大变化
  • 修改了全局的编译选项、内核配置、产品配置文件
  • 怀疑增量编译产物存在缓存污染或依赖不一致的问题
  • 需要产出干净的发布版本镜像
  • 首次搭建编译环境,验证整个工具链是否正常工作

全量编译还有一个隐藏的好处,就是当你对编译框架做了自定义修改后,它能帮你验证这些修改不会破坏其他模块。比如我在后续的实操中修改了产品配置文件,如果不做全量编译,很多依赖这个配置的模块不会重新触发构建,问题可能会被隐藏到运行时才暴露,那时候排查的代价就高多了。

2. 编译前的准备工作

2.1 硬件与系统环境要求

先说硬件。OpenHarmony全量编译对机器配置的要求不低。我用的是一台32GB内存的x86_64主机,固态硬盘预留了200GB以上空间。如果你只有8GB或16GB内存,也不是完全不能跑,但编译过程中可能会因为内存不足导致OOM(Out Of Memory),编译进程被直接杀掉。Ninja默认会根据CPU核心数并行执行任务,内存不够的时候最好限制一下并行任务数。

磁盘空间这块要特别注意。源码解压后大概10GB左右,但编译过程中产生的中间文件、out目录、预编译工具链、缓存文件加在一起,50到100GB都是正常的。我实测下来,完整的全量编译后,out目录大约占40GB上下,加上源码和工具链,总占用在60GB到80GB之间。所以磁盘至少留足100GB再开始,否则编到一半磁盘满了,前一晚的编译就白跑了。

操作系统方面,官方推荐Ubuntu 20.04或22.04 LTS,内核版本和glibc版本都有隐含要求。如果你用的是其他发行版,比如Debian、Fedora或者Arch,大概率会遇到工具链不兼容的问题。我不是说非Ubuntu不可,而是如果你不想在环境问题上花太多时间,直接用官方推荐的版本是最省心的方案。

2.2 软件依赖与Docker方案的选择

OpenHarmony的编译依赖一大堆软件包,包括但不限于:binutils、git、gn、ninja、clang、gcc、llvm、python3、nodejs、npm、openharmony的hb工具等。其中很多工具对版本有严格的要求。比如hb工具要求Python 3.7以上但又有特定版本的限制,Node.js要求14版本左右,太新或太旧都可能导致编译脚本报错。

这里我强烈建议使用Docker来搭建编译环境,而不是在宿主机上直接装依赖。

用Docker的好处有两个。第一,环境隔离,不会污染你的日常开发环境;第二,可复现,你记录下Dockerfile,团队里的任何一个人都能构建出一模一样的编译环境。

我当时的做法是拉取官方提供的OpenHarmony编译镜像,然后在镜像基础上做了少量定制。如果没有现成镜像,也可以用Ubuntu 20.04的官方镜像自己装依赖。需要注意,不要在容器里跑Windows子系统或者macOS虚拟机来编译,交叉编译产生的路径问题和权限问题会把你折磨到怀疑人生。

提示:编译过程中创建的文件非常多,在容器里编译时务必挂载一个足够大的数据卷到/home目录,并且不要使用Docker默认的虚拟磁盘大小,否则很容易在编译中途出现"No space left on device"。

2.3 源码获取与版本选择

源码获取用的是repo工具。OpenHarmony的代码量非常大,分成数百个Git仓库,手动一个个clone不现实,repo就是Google开发的用于管理多仓库的工具,OpenHarmony社区也沿用了这套机制。

获取源码前要先确定版本分支。我当时用的是OpenHarmony 3.2 Release版本,这个版本的代码相对稳定,社区资料多,踩坑时也更容易搜索到答案。如果你想跟上游保持同步,可以用master分支,但要注意主干代码可能包含一些未稳定的新特性,编译失败的概率会大一些。

repo初始化和同步的命令如下:

mkdir ohos && cd ohos repo init -u https://gitee.com/openharmony/manifest.git -b OpenHarmony-3.2-Release repo sync -c -j8

这里的-c表示只同步当前分支而不是全部分支,能省不少时间和带宽。-j8表示并发同步8个仓库,如果你的网络带宽足够大,可以调大这个数值,但太大会导致某个仓库连接被重置,反而适得其反。

源码同步完成后,记得检查一下各仓库是否都完整。repo sync偶尔会因为网络原因漏掉某些仓库,可以用repo forall -c 'git status'做一个全局检查,看看有没有处于异常状态的仓库。

3. 产品配置与设备树的选择逻辑

3.1 从"编译谁"到"怎么编译"

OpenHarmony的编译框架跟很多嵌入式Linux项目不同,它引入了一个"产品"的概念。产品配置决定了你要编译出什么样的系统镜像,包含哪些子系统、哪些驱动、哪些应用。所以编译的第一步不是敲make,而是先搞清楚你要编译的目标是什么。

我手头的开发板是RK3568平台。RK3568是瑞芯微推出的一款高性能处理器,在OpenHarmony生态中使用非常广泛,很多开发板都用这颗芯片。但同样是RK3568,不同厂家的开发板在硬件细节上会有差异,比如DDR容量、屏幕分辨率和接口类型、外设型号等。这些差异体现在设备树文件中,也体现在产品的配置文件里。

这里必须强调一个新手最容易搞不明白的问题:RK3568的设备树到底怎么选。

打开内核的arch/arm64/boot/dts/rockchip/目录,你能看到一堆rk3568开头的dts文件,比如rk3568-evb1-ddr4-v10.dtbrk3568-evb2-lpddr4x-v10.dtbrk3568-evb2-lpddr3-v10.dtb等等。这些文件对应不同规格的EVB板卡,你有两个选择:

第一,如果你的开发板是官方EVB板卡或者兼容EVB设计的板子,直接根据DDR类型选择对应的dts文件。

第二,如果你用的是第三方厂商的开发板,厂商一般会提供自己的设备树配置或者补丁,这个时候不要去动官方的dts文件,而是把你的板级配置作为一个新target添加到编译框架中。

我在第一次编译时就犯了想当然的错误。我直接用默认的rk3568-evb1-ddr4-v10.dtb,结果系统启动后触摸屏没有反应,串口信息也显示某个I2C设备初始化失败。后来排查发现,我的板子上触摸屏用的是不同的I2C地址,DDR类型也不同,必须用厂商提供的设备树补丁才能正常工作。

注意:选择设备树的本质是匹配硬件参数。DDR类型不匹配可能导致内存初始化失败或容量识别错误,现象是系统启动卡在u-boot阶段或者内核panic。外设不匹配则表现为驱动加载失败、设备节点不生成。所以在编译之前,务必确认你的开发板到底是基于哪个EVB设计,DDR是DDR3、DDR4还是LPDDR4X,这几个关键参数别搞混。

3.2 product配置文件里的门道

确定了设备树,接下来要看产品配置文件。OpenHarmony的产品配置主要放在vendor/product/目录下。以RK3568为例,在vendor/rockchip/rk3568/目录下有一个config.json文件,这个文件定义了产品的名称、厂商、子系统列表,以及产品属性。

这个文件的内容非常关键。比如product_name不能乱起,后面打包镜像的时候会用到。又比如subsystems列表决定了要编译哪些子系统,你不需要的子系统可以注释掉,这样能显著缩短编译时间。但是要注意,某些子系统之间是有依赖关系的,比如系统UI框架依赖图形子系统,图形子系统又依赖WMS和窗口管理。如果你不熟悉这些依赖关系就瞎裁剪,很容易编到一半报错或者编出来的系统缺东少西。

还有product_servicesbuild_version等字段,它们会写入最终系统镜像的属性中,在系统启动后可以通过param get命令查看到。这些信息在跟踪镜像版本时非常重要。

我个人的建议是,第一次全量编译的时候,不要做任何裁剪,先把整套系统完整编一遍。只有在完整编译通过、系统能正常启动的情况下,才去尝试裁剪或者定制。这样做可以帮你建立一个"我改了什么导致什么"的基线认知,否则出了问题你根本不知道是自己的修改引起的,还是本来就有问题。

4. 首次全量编译的完整实操

4.1 编译工具链的准备

在动手编译之前,先把必需的编译工具准备好。OpenHarmony使用的编译框架是GN+Ninja,在这之上又封装了hb工具。hb工具负责解析产品配置、生成ninja文件、调用ninja执行编译任务。

hb工具的安装方式在源码里就有脚本,在源码根目录执行即可:

python3 -m pip install --user build/hb

或者直接源码方式安装:

cd build python3 -m pip install --user .

安装完成后,执行hb env可以查看当前环境信息,能正常输出版本信息就说明安装成功了。如果提示找不到hb命令,多半是pip的bin目录没有加入PATH环境变量,手动加一下就好。

还需要确认几个关键工具的版本:

node -v # 建议v14.x python3 -V # 建议3.8及以上

4.2 设置编译目标与执行编译

源码根目录下执行hb set,会列出当前支持的所有产品列表。用键盘上下键选择你要编译的产品,我这边选的是rk3568对应的产品名。

选完之后,执行:

hb build -f

这里的-f参数表示全量编译。如果不加-f,hb默认只对增量部分做编译,如果你的out目录是空的,其实效果等同全量编译,但为了明确语义,建议还是加上。

编译过程中,屏幕上会持续滚动大量日志。我第一次看到这个日志的时候心里直打鼓,总觉得哪里要出错。实际上只要不是在结尾出现FAILED字样,绝大部分的WARNING都是可以忽略的。编译耗时跟机器性能直接相关。我用32核的机器全量编译一次大约需要40到50分钟。如果你用的是8核或者12核的机器,预计需要2到3小时,甚至更久。所以建议在编译开始前确保你的SSD有良好散热,或者干脆晚上挂机编。

4.3 编译输出物解读

编译完成后,产物都在out/目录下。以RK3568为例,主要输出在out/rk3568/子目录中。你会在里面看到几个重要的东西:

  • packages/phone/:系统镜像的打包中间目录,可以简单理解为最终系统的"根目录"
  • kernel/:编译好的内核镜像,包括uImage或boot.img等
  • dist/:最终的可烧录发布包
  • suites/:测试套件

其中dist目录下的文件就是你要拿去做烧录的。一般会有一个压缩包,包含boot.imgsystem.imgvendor.imguserdata.img等分区的镜像文件。

每个镜像文件背后的含义值得了解:boot.img包含内核和ramdisk,是系统启动阶段加载的第一个镜像;system.img包含系统服务和系统库;vendor.img包含厂商相关的驱动和配置;userdata.img是用户数据分区。这几个分区在烧录时分别对应不同的存储分区地址,不能混着烧。

4.4 打包相关参数与产物的关系

很多同学到了打包这一步就开始懵了,觉得打包是不是一个独立的概念。实际上在OpenHarmony的语境里,编译和打包是一体的。编译生成的每个模块产出物会先安装到packages/phone/目录,然后由一个打包脚本将这整个目录按分区划分规则制作成各个镜像文件,最后再组装成最终的分发包。

如果你只想单独打包某个镜像,比如只重新打包system.img,可以使用:

./build.sh --product-name rk3568 --build-target make_system

其中--build-target指定构建目标。make_systemmake_vendormake_boot分别对应打包不同的分区镜像。这个方式在后续做定制时非常有用,因为你不需要每次修改一个小地方就全量编一遍。

但要注意,单独打包某个镜像的前提是它依赖的模块已经编译好了。如果你修改了一个系统库并且该库被system.img包含,那么你得先确保这个库已经重新编译完成,再执行打包任务。

5. 镜像烧录与启动验证

5.1 烧录工具的选择

OpenHarmony推荐使用RKDevTool来给RK3568系列开发板烧录镜像。这个工具是Rockchip官方提供的,Windows版本和Linux版本都有。我用的Windows版本配合开发板的Maskrom模式来烧录。

烧录前需要准备一个USB Type-C数据线连接开发板与电脑,并通过开发板的烧录按键进入Loader模式或者Maskrom模式。不同板子的进入方式略有不同,有的需要按住烧录键再上电,有的需要在u-boot命令行执行特定命令,这里以你手头开发板的说明书为准。

5.2 分区表与烧录步骤

RKDevTool左侧有一个分区表,默认会列出loader、parameter、uboot、misc、boot、recovery、system、vendor、userdata等分区。每个分区对应一个镜像文件路径,你需要把编译生成的对应镜像填进去。

这里特别提醒一下,分区表中的起始地址和大小必须与parameter.txt保持一致。不同版本的parameter.txt定义了不同大小的system分区和vendor分区。如果你改动过分区大小,烧录的时候一定记得同步更新,否则会出现system空间不够或者数据覆盖的问题。

常规烧录步骤如下:

  1. 打开RKDevTool,加载编译出的loader.bin和各个镜像
  2. 开发板进入Loader模式,连接USB
  3. 点击"执行"开始烧录
  4. 烧录完成后开发板自动重启

烧录过程中如果出现设备掉线,先检查USB线是不是只有充电没有数据功能的线,这种线我之前踩过坑,换了一根带数据功能的线就好了。

5.3 启动日志的要害怎么看

系统启动后,通过串口连接开发板,你会看到完整的启动日志。串口工具的波特率一般是1500000,也就是1.5Mbps,别设置成115200,否则打印出来的全是乱码。

启动日志从u-boot阶段开始,然后是内核启动,最后是用户态init进程。重点关注这几个地方:

  • u-boot阶段:确认DDR容量识别是否正确,内核镜像加载地址是否正确
  • 内核阶段:确认设备树是否成功加载,有没有Unsupported device tree之类的致命错误
  • init阶段:确认各个服务是否正常拉起,尤其注意有没有进程反复crash重启

我在验证启动的时候发现一个问题:内核起来了但系统服务起不来,看日志发现是某个so文件加载失败。最后定位到是编译时子系统依赖没有选全,导致system分区里缺少对应的库文件。重新完整编译后问题就消失了。这种问题在全量编译下不太容易出现,但如果你做了裁剪定制,那就要格外小心。

6. 编译过程中我踩过的那些坑

6.1 环境问题导致的花式报错

编译过程中遇到的各种问题,我整理成一个速查表供大家参考。

问题现象常见原因解决方向
提示python3版本过低或过高系统python与hb要求不匹配安装Python 3.8~3.10版本或用pyenv管理
编译中途OOM被杀内存不足,并行任务太多限制-j参数或增加swap空间
磁盘空间不足out目录和缓存过大清理旧产物,扩大磁盘
node相关脚本报错Node版本不对切换到v14.x版本
提示缺少某个系统库依赖安装不完整对照官方依赖清单逐项检查
文件权限问题容器或挂载目录权限配置不当确保项目目录属主与当前用户一致

这里特别提一下swap的问题。很多开发者用虚拟机跑编译,虚拟机的默认内存可能只有4GB或8GB,这时候建议给虚拟机分配至少16GB内存,如果物理内存不够,建一个16GB的swap文件也能勉强跑通,但编译时间会明显变长。我自己实测过,swap跑全量编译比物理内存充足时慢30%以上,所以有条件还是物理内存管够比较好。

6.2 设备树选择的迷惑时刻

不夸张地说,RK3568设备树的选择是新手遇到最多问题的环节。我整理了三个典型的迷惑场景:

第一个,板子明明是DDR4 2GB,但默认配置走的是DDR4 4GB的设备树,结果系统启动后只识别出2GB内存或者直接启动失败。

第二个,不同厂商的板子虽然同属RK3568,但在GPIO复用上完全不同,比如有的板子把某个GPIO用作LED,有的作为按键,这些差异必须通过设备树来体现。

第三个,内核自带的dts文件往往只适配官方EVB板,第三方板卡如果直接用官方dts,很多外设会处于未初始化状态。

如果你是用的第三方板卡,最稳妥的做法是联系厂商要适配好的内核源码或补丁。如果厂商只是给了你一个dtb文件,你仍然需要将它放到内核源码的对应位置,并在编译配置中指定使用这个dtb。这个过程不难,但需要你理解dts文件的编译和打包机制。

6.3 打包镜像后启动不了的排查思路

如果你打包出来的镜像烧录后无法启动,不要慌,按照下面的思路来排查:

先看u-boot阶段有没有日志。如果u-boot阶段就黑屏无输出,多半是loader烧录问题或者DDR初始化失败。如果u-boot正常但内核没起来,多半是boot.img的问题,检查内核地址和dtb是否匹配。如果内核起来了但卡在某个驱动初始化上,多半是设备树外设配置的问题。如果用户态起来又反复重启,重点看init日志和崩溃服务的日志。

很多新手一看到U-Boot SPL提示就以为系统正常,实际上那只是第一个阶段。完整启动有三个阶段,每个阶段有各自的日志输出位置和特征,不要只看前面就下结论。

7. 自定义配置与二次编译的心得

7.1 裁剪和新增子系统的实践

全量编译跑通之后,下一步通常就是做定制。你要么想去掉一些用不到的功能减小镜像体积,要么想加入自己的应用或驱动模块。

新增子系统的方式是在产品的config.json中加一行依赖,然后在build目录下确保你的组件有对应的BUILD.gn文件。GN文件的编写逻辑跟CMake类似,用ohos_shared_libraryohos_executable等模板声明你的构建目标。

这里有一个比较隐蔽的坑:新增了一个组件后,如果你只是执行hb build -f,有时候新组件并不会被打进镜像。这是因为镜像的生成规则有独立的依赖关系,你得确认你的组件被某个分组目标引用了,比如group("system_group")或类似的目标。没有被引用的目标即使编译了,最终也不会出现在image里。

7.2 使用增量编译提升开发效率

全量编译适合作为基线验证,平时的开发迭代建议使用增量编译。改动内核代码后,执行:

./build.sh --product-name rk3568 --build-target kernel

改动某个系统服务后,先单独编译该服务并验证,再决定是否重新打包系统镜像。

增量编译在90%的场景下是没问题的,但如果你跨版本升级源码,或者修改了全局的编译参数,增量编译可能不会触发所有相关模块的重新构建,这时候一次全量编译反而能帮你节省排查时间。我的经验是:增量编译连续超过一周没做过全量验证时,主动做一次全量编译,把隐患掐死在摇篮里。

7.3 基于OpenHarmony的典型应用场景扩展

编译打包这个技能的价值,远不只是能刷个系统而已。往大了说,它是一切OpenHarmony系统定制工作的基础。

  • 如果你是做智能硬件的,需要裁剪系统,把不需要的子系统去掉,把镜像控制在最小体积,这就依赖你对编译配置的深入掌握
  • 如果你是做行业解决方案的,需要在标准系统上预装自家应用、配置特定网络策略、添加设备标识,这些都发生在编译打包阶段
  • 如果你做的是发行版厂商,那就更需要掌握全量编译、版本管理、可持续集成这些能力了

我看到很多团队只停留在应用开发层面,对系统构建一窍不通,一旦遇到需要定制系统的需求就抓瞎。实际上,把全量编译和打包这套流程跑通,你的开发能力才算是真正从应用层延伸到了系统层。

8. 一些想对新手说的经验之谈

之前有朋友问我,第一次做全量编译最大的感受是什么。我想了想,最大的感受是——编译不是最难的部分,理解你正在编译的东西才是。

整个OpenHarmony全量编译链路中,真正考验人的地方在于:当你面对一个错误时,你要能判断它来自工具链、源码、配置还是环境。我的建议是不要急着复制错误日志去搜索,先自己读一遍日志,找到第一个出现的错误位置,那往往才是真正的根因。后面的很多错误可能都是前一个错误的连锁反应,就像高速公路上连环追尾一样,你只需要处理第一辆车。

另外,养成每次编译前记录当前代码状态的习惯。你可以用git log --oneline -5记录当前版本,用repo manifest -r -o manifest.xml保存所有仓库的精确版本。这样一来,编译出问题了可以回溯,编译成功了也可以准确记录产物的代码来源。这个习惯在后续做版本发布和问题定位时价值极大。

还有一个建议是,不要怕看编译日志,也不要被编译日志淹没。我在第一次编译时尝试把日志完整看完,结果发现根本看不完,后来学会了有重点地看:编译开始看配置摘要、编译中间看错误关键字、编译结尾看总结。用hb build -f 2>&1 | tee build.log把日志存下来,出错了搜索FAILED关键字,这样做问题定位效率是最高的。

最后再分享一个小经验:如果你在编译过程中遇到了一个网上搜不到答案的问题,大胆去看源码。OpenHarmony的构建脚本、编译工具链都组织得很清晰,直接到build/目录下搜索报错信息中的关键字,往往能找到判定的逻辑,理解它为什么报错。这个"读源码排查问题"的能力,是比编译本身更有价值的收获。至少我自己在解决了几个这样的问题之后,对这套系统的构建机制算是真正入了门。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询