从“本地能编译“到“流水线稳定出包“:HarmonyOS CI 构建问题复盘

本地用 DevEco Studio 点一下 Build,三秒就出包了。推到 CI 流水线,直接红。最开始以为是网络问题,重试了几次,还是红。翻 CI 日志一看,报错信息和本地完全不一样——本地根本不会报这个错。
这种问题最磨人:代码是你的,本地跑得好好的,一到 CI 就挂。而且每次排查都像开盲盒,这次是 SDK 版本不对,下次是签名文件找不到,再下次是依赖缓存出了问题。后来花了时间把环境统一、配置锁死、检查规则写进脚本,CI 才真正稳下来。
这篇复盘一次真实的 CI 构建事故,讲讲排查顺序、常见坑和怎么把一次性修复变成长期规则。
一、本地正常,CI 第一次就失败
第一次接 CI 的时候,我以为把代码推上去,流水线自动跑一下 build 命令就行了。结果第一次跑就挂了,报错是 SDK 版本不匹配。
本地 DevEco Studio 用的是我机器上装的 SDK,CI 环境里是另一个版本。本地的 node 版本和 CI 镜像里的也不一样。这两个不一样,构建结果当然不一样。
这个阶段最容易犯的错是:看到 CI 报错就去改代码。但本地明明能编译,改代码有什么用?问题根本不在代码,在环境。先把环境对齐了再说,别一上来就动工程配置。
二、先排环境,再排工程配置
排查顺序很重要,先从最外层的环境开始,逐层往里走。
第一步看 CI 镜像里的 Node、Hvigor、SDK 版本和本地 DevEco Studio 用的是不是一致。DevEco Studio 自带了一套 Node 和 Hvigor,但 CI 环境通常用全局安装的版本,两边对不上就会出各种莫名其妙的错误。
第二步看环境变量。本地 DevEco Studio 构建时会自动设置一些环境变量,比如 SDK 路径、签名文件路径这些。CI 环境里这些变量得手动配,漏了任何一个都会构建失败。我之前遇到过一次,CI 日志报"找不到签名文件",查了半天才发现是环境变量里的 KEYSTORE_PATH 没配对。
第三步看依赖。CI 上拉的依赖版本和本地 lock 文件里的是不是一致。如果 oh-package.json5 里用了范围版本号(比如 ^1.0.0),CI 上每次拉到的小版本可能不一样,今天能跑明天就挂。
这个排查顺序的核心思路是:本地能跑说明代码本身没问题,问题一定出在本地和 CI 的差异上。先把所有外部依赖的差异找出来,再去看工程配置。
三、依赖锁定和 Hvigor 参数为什么必须统一

环境版本对齐以后,下一个坑是依赖和构建参数。
依赖一定要锁定。oh-package-lock.json5 必须提交到仓库里,CI 构建时必须用 `--frozen-lockfile` 之类的参数确保不自动升级依赖。不锁版本的话,CI 上每次构建拉到的依赖版本都可能不同,出了问题根本没法复现。
Hvigor 的构建参数也要统一。本地 DevEco Studio 点 Build 时用的参数,和 CI 命令行跑的参数可能不一样。比如本地默认是 Debug 构建,CI 可能默认 Release;本地开了增量编译,CI 上可能是全量。这些差异都会导致构建结果不同。
下面这段配置放在 build-profile.json5 里,统一 Debug 和 Release 的构建参数。关键是把签名配置和混淆开关在这里集中管理,不要散落在多个地方。
{
"app": {
"signingConfigs": [
{
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "./cert/release-cert.p12",
"storePassword": "store-password-placeholder",
"keyAlias": "debugKey",
"keyPassword": "key-password-placeholder",
"profile": "./cert/release-profile.p7b"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
这段配置要解决的问题:签名路径和 SDK 版本在这里统一声明,CI 和本地读同一份配置,不会出现本地用 DevEco 自动签名、CI 上找不到签名文件的情况。
实际使用时要注意:certpath 里的证书文件不能提交到公开仓库,应该放在 CI 的密钥管理里,构建时动态写入。上面代码里的 storePassword 和 keyPassword 是占位符,真实项目里应该从环境变量读取,不能硬编码在配置文件里。compatibleSdkVersion 要和 CI 镜像里安装的 SDK 版本对应,版本号写错了 CI 就会报 SDK 不匹配。
四、签名和 Debug/Release 差异怎么处理
签名是 CI 上最容易出问题的环节。本地 DevEco Studio 会自动用调试签名构建,所以本地 Build 从来不报签名错误。但 CI 上跑 Release 构建的时候,需要正式签名证书,证书没配好就直接挂。
Debug 和 Release 的差异还要注意:混淆开关、资源压缩、代码优化这些,Release 下通常是开的,Debug 下是关的。Release 开了混淆以后,某些反射调用会出问题——本地 Debug 跑着没问题,CI Release 一编就报错。这种问题排查起来特别花时间,因为你本地默认跑的是 Debug,复现不了。
我的做法是在 CI 上同时跑 Debug 和 Release 两次构建,确保两种模式都能过。这样 Release 模式下的问题在 CI 上就能暴露出来,不用等发到应用市场才发现。
五、最后把一次性修复变成长期构建规则
每次 CI 挂了以后手动排查一遍,修好了下次又出别的问题。后来把排查过程中确认有效的检查项写进了 CI 脚本里,每次构建自动跑一遍,问题在出包之前就拦住了。
脚本里做了这几件事:检查 Node 和 Hvigor 版本是否符合要求;检查 lock 文件是否最新;构建前清理旧缓存;Debug 和 Release 分别构建一次;构建完成后检查输出 hap 文件是否存在。
下面这段是 CI 构建脚本的核心部分,放在 pipeline 的 build 阶段。它先做环境检查,再用锁定的依赖构建,最后验证产出物。
#!/bin/bash
set -e
echo "=== Environment Check ==="
node -v
hvigorw --version
echo "=== Install Dependencies (frozen lockfile) ==="
ohpm install --frozen-lockfile
echo "=== Build Debug ==="
hvigorw assembleHap --mode project -p product=default -p buildMode=debug
echo "=== Build Release ==="
hvigorw assembleHap --mode project -p product=default -p buildMode=release
echo "=== Verify Output ==="
ls -la entry/build/default/outputs/default/*.hap
echo "=== Build Done ==="
这段脚本要解决的问题:每次 CI 构建都用同样的流程——冻结依赖版本、分别构建 Debug 和 Release、最后验证产出物存在。环境检查的输出会打进 CI 日志,出问题时一眼就能看到版本对不对。
实际使用时要注意:--frozen-lockfile 是关键,如果 lock 文件和 oh-package.json5 不一致它会直接报错,强制开发者同步更新。hvigorw 的具体参数需要对照当前版本的命令行文档,不同版本参数可能有差异。产出物验证那一步不能省——有时候构建"成功"了但输出目录里没有 hap 文件,这种问题只有检查了才知道。

CI 构建这件事,本质上是在把"我本地能跑"变成"任何人在任何环境都能跑"。环境对齐、依赖锁定、参数统一、自动检查,做到这几点以后,CI 才不会天天出幺蛾子。
更多推荐




所有评论(0)