1. 先搞清楚 pub_release 到底在解决什么问题
1.1 没有自动化之前,发布一个 Flutter 三方库有多痛苦
做过 Flutter 三方库维护的应该都有同感,真正磨人的不是写代码,而是发布。我最早维护一个开源包的时候,每次发版流程是这样的:手动改pubspec.yaml里的版本号,手动整理 CHANGELOG,跑一遍flutter analyze和测试,然后dart pub publish --dry-run检查有没有遗漏文件,最后再执行真实发布,等 pub.dev 那边构建通过,还要打 git tag、推远端。这一套下来快的话二十分钟,慢的话半小时以上,而且每一步都靠人肉盯着,稍不留神就出岔子。
最常见的翻车现场有三个:版本号改了但 CHANGELOG 忘更新,仓库 tag 和 pub.dev 上的版本对不上,以及发布包里的文件没过滤干净把密钥或者临时文件打进去了。这些都属于低水平但高频率的错误,偏偏又特别消耗信任,用户一发现你发布的包有问题,下一次用之前就会犹豫。所以当我第一次看到pub_release这个工具的时候,第一反应是终于有人把“发布”这件事当成正经工程来做了。
1.2 pub_release 的核心能力拆解
pub_release是一个专门面向 Dart/Flutter 包维护者的发布自动化工具,它做的最核心的一件事情,就是把“版本决策 + 发布动作 + 信息同步”整合成一条命令。你可以通过dart run pub_release publish来触发发布,它内部会帮你完成语义化版本号的自动递增、CHANGELOG 的生成与更新、git tag 的创建与推送,以及在真正pub publish之前自动跑一遍 dry-run 校验。
这个工具最大的价值在于它把发布流程变成了一个可重复、可审计的过程。版本号怎么升,升大版本还是小版本,是 break change 还是 feature,这些决策由你下,但是执行过程由工具统一完成。团队协作时尤其好用,不再依赖某个“记得住全部流程”的个人,新人照着文档跑命令也能发布。这也是我后来下决心做鸿蒙化适配的原因——鸿蒙生态的开发链路已经逐渐成熟,但发布工具链这块还存在不少断档,值得花时间补上。
1.3 鸿蒙化适配到底改什么:不是代码移植,是流程再造
很多人一听“鸿蒙化适配”,第一反应是代码怎么跨平台编译。但pub_release不一样,它是纯 Dart 写的工具,理论上跑在哪里都行。真正的适配难点在于:它的发布目标原先只有 pub.dev,而鸿蒙生态的三方库发布目标并不是 pub.dev,而是 OpenHarmony 三方库中心仓,用的是ohpm publish。这意味着我要保留pub_release的版本管理、CHANGELOG 生成、流程编排能力,同时把发布动作换成鸿蒙生态的那套逻辑。
所以“鸿蒙化适配”这个题目,本质上是在做一次发布流程的再造。你需要同时考虑两个生态的双重发布需求:pubspec.yaml和oh-package.json5双元信息同步,pub.dev 与 ohpm 仓库的版本对应关系,以及 Flutter 插件在鸿蒙侧的ohos目录能否被正确校验。这些都梳理清楚之后,才算把“发布”这个事在鸿蒙环境下盘活了。
2. 鸿蒙化适配的整体设计与工具链选型
2.1 双元信息同步:pubspec.yaml 与 oh-package.json5
鸿蒙化适配过程中我踩到的第一个坑就是双元信息同步。Flutter 插件如果要跑到鸿蒙上,光有标准的 Flutter 工程还不行,OpenHarmony 侧一般会要求你维护一份oh-package.json5,里面声明这个包的名称、版本、依赖和 RPC 标记等元数据。
一开始我的处理方式很粗暴:每次发布前手动同步两份文件的版本号。结果必然翻车,某一版忘了同步,Flutter 侧引用的是 1.2.0,鸿蒙侧 ohpm 拉到的还是 1.1.2,排查了大半天还以为是缓存问题。后来我把这个同步动作硬编码进了发布流水线,用脚本读取pubspec.yaml里的version字段作为唯一真源,再覆盖写入oh-package.json5的对应字段。核心思路就是:永远不要让两份元数据各自独立演进,必须定义一个唯一真源,剩下的靠程序去对齐。
# 伪代码示意:同步版本号 PUBSPEC_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | tr -d "'\"") OH_PACKAGE_FILE="oh-package.json5" sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$PUBSPEC_VERSION\"/" "$OH_PACKAGE_FILE"需要注意的是oh-package.json5是 JSON5 格式,支持注释和单引号,每个项目的写法多少有点差异,所以 sed 的匹配规则要根据实际情况调整。我建议先在自己的工程里跑一次 dry-run,确认版本号字段能正确替换之后再纳入 CI。
2.2 流水线架构:如何把 pub_release 嵌进鸿蒙生态
聊到流水线架构,核心问题就一个:原有pub_release的发布动作还保留吗?答案是分场景。鸿蒙化并不意味着放弃 pub.dev,恰恰相反,一个真正合格的 Flutter 三方库最好做到一套代码同时发布到两个仓库,让 Android、iOS、鸿蒙的开发者都能很方便地用你的包。
流水线分层上我是这样设计的:
- 第一层:版本决策层。保留
pub_release的语义化版本递增策略,通过参数控制是发 major、minor 还是 patch 版本。 - 第二层:元数据同步层。版本号确定之后,自动完成
oh-package.json5版本同步以及 CHANGELOG 的追加。 - 第三层:构建验证层。跑 Flutter 侧的分析与测试,同时检查鸿蒙侧的
hvigor是否能正常装配依赖。 - 第四层:发布执行层。按需并行发布到 pub.dev 和 ohpm 中心仓。
每一层之间用脚本解耦,允许单独执行。这样本地开发和 CI 跑的是同一套逻辑,不会出现“本地能发,CI 挂了”的割裂情况。
2.3 工具链选型:ohpm、hvigor、DevEco Studio 的角色分配
鸿蒙生态的工具链跟 Flutter 侧差别很大,这里简单划线:
ohpm是鸿蒙生态的包管理器,对标 pub.dev 的发布通道,负责包的下载和发布,命令格式类似ohpm publish。hvigor是鸿蒙侧的构建任务框架,对标 Gradle,负责处理 OpenHarmony 工程里的编译任务,Flutter 插件的鸿蒙原生部分最终会通过它构建成 HAR 包或 HAP。- DevEco Studio 是集成开发环境,它内置了 SDK 管理、签名配置和调试工具。CI 环境里一般不需要它,但本地联调排查签名问题时绕不开。
我实际跑下来的经验是:CI 环境最好预装并使用与本地一致的 ohpm SDK 和 hvigor 版本,否则非常容易出现本地编译通过、CI 构建失败的情况。鸿蒙工具链的版本演进很快,API 版本和构建工具版本一旦错位,报错信息还很隐蔽,后面我会专门列几个典型问题。
3. 核心实操:把发布流水线真正跑起来
3.1 第一步:建立可控的目录与版本基线
动手之前先把工程的目录结构整理干净。因为pub_release发布时会自动收集目录下的文件,如果一个工程里既有 Flutter 标准结构又有鸿蒙的ohos目录,文件过滤规则一定要提前设计好,否则发布出去的包里会出现大量无效文件。
我建议的目录基线大致如下:
my_flutter_package/ ├── lib/ # Dart 实现 ├── ohos/ # OpenHarmony 平台实现 │ ├── entry/ │ └── build-profile.json5 ├── test/ # 测试用例 ├── tool/ # 发布脚本与工具类 │ ├── sync_version.sh │ └── publish_harmony.sh ├── pubspec.yaml ├── oh-package.json5 └── CHANGELOG.md版本基线方面,我习惯把pubspec.yaml里的version作为唯一真源,oh-package.json5和 CHANGELOG 都从这个源头派生。这样做的好处是减少人工操作点,因为每一次人工介入都是一次出错的机会。你只需要在发布时告诉pub_release要大版本还是小版本,剩下的全是机器在做。
3.2 第二步:写好自动化脚本(核心)
脚本是整个鸿蒙化适配的心脏。我会把过程拆成两个脚本:sync_version.sh负责元数据同步,publish_harmony.sh负责鸿蒙侧的发布。前者逻辑刚才已经讲过,后者要处理的事情更复杂一些。
publish_harmony.sh的大体逻辑如下:
- 检查 ohpm 是否登录,没有 token 就提前报错。
- 校验
oh-package.json5的版本号跟pubspec.yaml一致。 - 执行
ohpm build或者通过 hvigor 构建 HAR,确保鸿蒙侧产物没问题。 - 最后执行
ohpm publish。
# publish_harmony.sh 关键片段 set -e # 1. 登录态校验 ohpm whoami >/dev/null 2>&1 || { echo "ohpm 未登录,请先执行 ohpm login"; exit 1; } # 2. 版本一致性校验 PUBSPEC_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}') OHPM_VERSION=$(grep '"version"' oh-package.json5 | sed 's/.*: "\(.*\)",*/\1/') if [ "$PUBSPEC_VERSION" != "$OHPM_VERSION" ]; then echo "版本不一致: pubspec=$PUBSPEC_VERSION ohpm=$OHPM_VERSION" exit 1 fi # 3. 鸿蒙侧构建 hvigorw --mode ohos -p product=default assembleHar # 4. 发布 ohpm publish注意set -e一定要加上,任何一个环节失败都要中断,绝不能带着失败状态继续往下走。我见过不少 CI 配置忽略了这个细节,表面看流水线是绿的,实际上发布动作根本没执行成功。
3.3 第三步:接 CI 并加上发布验证
本地脚本跑通之后,就要把它接到 CI 上了。我的建议是发布动作不要由开发者本地触发,而是通过 Git tag 触发 CI 任务。开发者推送一个v1.2.0的 tag 到远端,CI 检测到之后自动执行全流程。这样发布记录和代码记录是对齐的,审计的时候非常方便。
CI 任务的阶段划分可以参考:
- 阶段一:安装 Flutter SDK 和 OpenHarmony SDK 工具链。
- 阶段二:跑
dart pub_release publish --dry-run,注意这个 dry-run 不真正发布,只做校验。 - 阶段三:执行版本同步脚本,确认
oh-package.json5被正确更新。 - 阶段四:运行 Flutter 单测和鸿蒙侧的编译验证。
- 阶段五:真正执行发布,发布成功后自动打上发布完成标记。
这里特别提一个细节:CI 里的 OpenHarmony 环境变量要显式设置。尤其是DEVECO_SDK_HOME和OHPM_REGISTRY,不同机器上 SDK 路径不一样,如果在脚本里写死绝对路径,换一台机器就废了。最好在 CI 配置里统一注入,脚本只读取环境变量。
# CI 环境变量示例 export DEVECO_SDK_HOME=/opt/ohos-sdk export OHPM_REGISTRY=https://ohpm.openharmony.cn/ohpm/3.4 第四步:验证环节不能省的东西
回到开头那个痛点,自动化最怕的就是把错误也自动化了。因此验证环节是这条流水线的生命线,必须包含以下几类校验:
- Dart 侧静态分析:
flutter analyze必须零 error,warning 视团队规范决定是否拦截。 - 测试套件:核心逻辑的单元测试必须通过,没有测试的模块不允许发布。
- dry-run 发布校验:
dart pub publish --dry-run会列出即将打包的所有文件,人工或脚本检查有没有多余文件混入。 - 鸿蒙侧构建产物校验:确认 HAR 包能正常产出,且
oh-package.json5里声明的依赖都能被解析。
我团队里的规矩是,以上四项任何一项失败,流水线直接红,发布者需要在 PR 描述里解释原因。这套机制跑起来之后,发布出问题的概率明显下降。
4. 实跑中的坑与排查实录
4.1 鸿蒙化适配常见问题速查表
这里把我实际跑溪中遇到的典型问题整理成了一张速查表,按症状、原因、解法三列来写,省得大家再踩一遍。
| 症状 | 根本原因 | 解决办法 |
|---|---|---|
| ohpm 发布时提示版本已存在 | oh-package.json5版本号未递增,重复发布同版本 | 执行同步脚本后检查版本号;确认pub_release已完成版本递增 |
| CI 里 hvigor 构建失败,本地却能过 | 本机与 CI 的 OpenHarmony SDK 版本不一致 | 锁定 SDK 版本,CI 与本地统一安装指定版本 |
ohpm publish报 401 错误 | token 失效或未登录 | 重新执行ohpm login获取 token,并在 CI 密钥管理里更新 |
| 发布后的 Flutter 包在鸿蒙侧 import 失败 | ohos目录下的原生实现未被正确打包 | 检查插件工程结构,确认oh-package.json5中的 main 入口有值 |
| CHANGELOG 里版本号和实际发布版本不一致 | 手动改过 CHANGELOG,绕过工具 | 禁止手动维护 CHANGELOG,统一交给脚本追加 |
| pub.dev 发布成功,ohpm 发布失败 | 双通道发布,其中一个通道执行异常 | 确保流水线里两步发布相互独立,失败一方可单独重试 |
4.2 我反复踩的三个典型坑
第一个坑是oh-package.json5的注释兼容问题。这玩意儿虽然是 JSON5,但有些工具链在解析时对注释的容忍度不一样。有一回我为了在配置文件里写一行说明文字,加了一个//注释,结果ohpm publish直接解析失败。后来我查了一下,才知道版本较旧的 ohpm 对 JSON5 的注释支持不完整。解决方案很简单:要么不用注释,要么升级 ohpm 到新版本。
第二个坑是版本号前缀不一致。Flutter 侧习惯用1.2.0这种三段式版本号,但部分鸿蒙生态的包会在版本号前加@或者带 build 号,比如1.2.0-rc.1。如果你的oh-package.json5里版本号被手动加上了奇怪的后缀,而pubspec.yaml里没有,同步脚本就给出来的version:判断失败。踩过一次之后,我在脚本里加了严格的版本号正则校验,不合法直接拒绝发布。
第三个坑是CI 里的 JAVA_HOME 影响 hvigor 构建。hvigor 构建底层依赖 Gradle,而 Gradle 是需要 JDK 的。CI 机器上如果同时存在多个 JDK 版本,而JAVA_HOME没有明确指向 JDK 17,构建过程就会报一些莫名其妙的错误,比如Unsupported class file major version。排查了半天才发现是基础镜像里默认 JDK 版本太老。这个问题后来通过在 CI 脚本里显式导出JAVA_HOME解决。
5. 这套流水线还能怎么扩展
5.1 从“发布工具”到“研发流程基础设施”
适配完pub_release之后,我最大的感受是:这已经不是一个简单的发布工具适配了,它实际上在帮你搭建研发生命线的“最后一段”。
你可以继续往上扩展,比如在版本同步阶段自动生成“发布说明卡片”,把 CHANGELOG 里的重要变更转成团队群里的公告;或者把每次发布的产物 metadata(版本号、发布时间、提交哈希)写进一个自动化报表页面;再进一步,还可以把发布流水线与 issue 自动关联,当某个 PR 合入并触发发布时,自动关闭对应的 issue。这些扩展都是在同一套版本决策机制之上做文章,并不会增加太多额外成本。
5.2 我在实际维护中形成的一个习惯
最后分享一个我自己坚持了很久的习惯:每次发布之后,我会手动去看一眼两个仓库上拉取到的包内容,确认无误之后才在群里发版本公告。自动化能保证流程稳定,但不能百分之百保证结果符合预期,保持一点人工复核的仪式感,反而能让发布这件事在团队里传递出一种“可靠”的信号。尤其是鸿蒙侧的三方库生态还处在快速演进期,多加一道检查,省下来的可能是后面一连串的线上问题。
这个适配方案的完整链路跑通之后,我已经把 Flutter 三方库发布到鸿蒙生态的流程固化成了团队标准动作,整个过程的耗时从过去的一小时压缩到五分钟以内,而且出错率大幅下降。如果你也在维护跨端 Flutter 库,并且打算支持鸿蒙,希望这套从工具理解、流程设计到脚本落地的思路能对你有些启发。