说实话,接触 Flutter for OpenHarmony 大半年,最让我头疼的倒不是框架本身的适配问题,而是每次发版都要手动敲那一长串构建命令。项目一多、模块一拆,光记住不同模块的构建参数就够喝一壶的了。有一次我数了一下,我们项目的构建、打包、测试、签名、推送居然有 17 种组合,而且不少命令带不同参数,谁记错一个,构建就直接废掉,浪费二十分钟。后来我把 derry 这个 Dart 生态里的脚本管理工具搬进了鸿蒙项目里,所有操作收敛成一个简单的 derry xxx,整个工作流一下子顺了不少。这篇文章就记录一下完整的落地过程,以及我踩过的那些坑,希望对正在折腾 Flutter for OpenHarmony 的兄弟们有点帮助。
derry 本质上是一个脚本管理工具,你可以把它理解成 Dart/Flutter 项目里的 “Makefile” 或者 “npm scripts”。以前我们习惯在项目根目录丢一堆 .sh 脚本,或者在 README 里写满命令,这样既不好维护,跨平台也是个问题。derry 用一份 derry.yaml 配置文件,把所有命令分类管理,支持环境变量、命令组合、脚本间调用,在一台 Windows 开发机、一台 macOS 开发机上都能跑,完全不依赖 bash。这玩意儿放在 OpenHarmony 项目里,就是现成的工作流加速引擎。
1. 项目背景:为什么鸿蒙 Flutter 项目需要脚本控制台
1.1 从一次让人崩溃的手动构建说起
我要先交代一下项目背景,不然你可能觉得我在小题大做。当时我接手的这个项目是基于 Flutter for OpenHarmony 开发的智能家居中控应用,运行在开源鸿蒙设备上。整个工程被拆成了三个模块:核心业务层、设备通信层、UI 展示层。每个模块分开开发,合到一起又需要按特定顺序构建。平时测试要打 debug 包,给客户演示要打 release 包,偶尔还要带上 mock 数据开关。这些操作全部要靠手动敲命令,一旦敲错一个参数,轻则重新跑一遍,重则产物搞混,给出错的包给到测试同学,人家测了半天发现是旧版本。这类问题我相信不止我一个人遇到。
OpenHarmony 的构建链路本身就要比普通 Android 项目复杂,它涉及 HAP 包的生成、签名、安装到模拟器或真机,每一步都有对应的命令。如果团队里每个人都凭记忆在终端里敲,效率低不说,还非常容易出错。我当时的想法很简单:能不能把这些命令抽象成一个个有名字、可复用的“脚本”,跑构建就敲 derry build:release,跑测试就敲 derry test:unit,跑全量检查就敲 derry check:all。这样不管是新同事还是老同事,谁都不会记错,也不会漏步骤。
1.2 derry 到底是什么,凭什么能管住项目工作流
说到 derry,可能很多 Flutter 开发者还不知道这个库,它是 Dart 生态里一个专门做脚本管理的开源三方库。用法和 npm scripts 很像,但它是给 Dart/Flutter 项目设计的。你可以在项目根目录放一个 derry.yaml,然后在里面注册各种脚本命令,之后通过 derry <脚本名> 来执行。derry 会负责解析命令、执行命令、显示输出、传递参数,还能做组合编排,本质上就是一个轻量级的任务运行器。
你可能要问了,我自己写 shell 脚本不行吗?也不是不行,但有几个痛点:
- 跨平台:shell 脚本在 Windows 上跑不了,而 OpenHarmony 开发团队里 Windows 和 macOS 混用的情况很常见。derry 底层是 Dart 虚拟机,天然跨平台。
- 可读性:一个
.sh文件写长了以后,维护非常痛苦。derry.yaml 是纯 YAML 格式,命令归类在scripts节点下,结构一目了然。 - 交互性:derry 在执行命令的时候能给出清晰的状态反馈,哪个步骤失败了一眼就能看到,比在 shell 里一坨输出里找 error 强太多了。
- 依赖管理:derry 可以通过
derry pub get之类的内置命令和 Dart 生态打通,也可以直接调用 flutter 命令,跟 OpenHarmony 开发链路完全兼容。
所以我最终选了 derry,而不是自己造轮子。这个决定在项目后期证明非常正确,因为随着工作流越来越复杂,脚本配置的维护成本几乎为零,改一个 YAML 节点就够了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:把 derry 接进 OpenHarmony 工程
2.1 Flutter for OpenHarmony 环境搭建的几点提醒
要先跑通 Flutter for OpenHarmony,环境本身就有几个门槛。首先 OpenHarmony 的 Flutter SDK 并不是官方主干分支,需要使用适配 OpenHarmony 的 fork 版本,具体来说就是把 Flutter SDK 切换到 OpenHarmony 适配分支,同时还要保证 Dart SDK 版本匹配。很多人在这一步就卡住了,不是因为不会装,而是因为版本对不上。
我的建议是,环境变量一定要配置规范,尤其是在 macOS/Linux 上。.bashrc 或 .zshrc 里至少要有这样几个变量:
bash复制export FLUTTER_HOME=/path/to/flutter_sdk_ohos
export PATH=$PATH:$FLUTTER_HOME/bin
export OHOS_SDK_HOME=/path/to/ohos_sdk
flutter doctor 检查的时候,要确保能识别到 OpenHarmony 相关的工具链。不同适配版本检测情况略有差异,有的版本 flutter doctor 看不出 OpenHarmony 支持情况,需要手动用 flutter config --enable-openharmony 之类的命令显式开启,这点要留意一下。
2.2 安装 derry:全局安装与项目级安装怎么选
derry 有两种安装方式,一种是通过 dart pub global activate 全局安装,另一种是作为项目依赖装进 pubspec.yaml。这两种方式我都试用过,说下我的感受。
全局安装的好处是,任何目录下都能直接敲 derry 命令,不需要进入到某个具体项目。它的坏处是版本管理相对来说粗放一点,如果多个项目用的 derry 版本不一致,偶尔会有兼容性问题。项目级安装的好处是版本锁定在 pubspec.lock 里,团队协作的时候大家拉下来依赖一致,不会出现我这边能跑你那边报错的情况。所以我的建议是,在 CI 流水线和团队协作场景里用项目级安装,本地日常开发可以用全局安装,两者不冲突。
全局安装命令如下:
bash复制dart pub global activate derry
安装完成后,命令行里直接输入 derry,就能看到帮助信息。如果是项目级安装,在 pubspec.yaml 的 dev_dependencies 里加上:
yaml复制dev_dependencies:
derry: ^1.4.0
然后执行 flutter pub get 或者 dart pub get 安装即可。安装好之后,在项目根目录新建一个 derry.yaml,derry 会默认读取这个文件。
2.3 目录规划与第一个 derry.yaml
接入 derry 之前,我建议先把项目目录稍微规划一下,这样脚本写起来会清爽很多。常见的做法是把所有和工程化相关的目录统一放好:
text复制project_root/
├── lib/ # Flutter 业务代码
├── test/ # 单元测试
├── integration_test/ # 集成测试
├── tool/ # 自定义 Dart 工具脚本
├── scripts/ # 传统的 shell 脚本(如果需要)
├── derry.yaml # derry 脚本配置
├── pubspec.yaml
└── README.md
然后我们写一个最简单的 derry.yaml 来验证工具链是否通了:
yaml复制scripts:
hello: |
echo "hello derry"
doctor: |
flutter doctor
在终端执行 derry hello,如果能看到输出,说明 derry 已经能正常工作了。这时候再试试 derry doctor,确认 Flutter 环境也没问题。这一步跑通之后,后面所有脚本都只是在 derry.yaml 里做配置而已,边际成本非常低。
3. derry 脚本控制台核心实现
3.1 脚本语法详解:从单条命令到组合编排
derry.yaml 的核心就是 scripts 这个顶层节点,每个脚本名字对应一段命令。最简单的方式是给一个脚本名赋值一个字符串命令,这种方式适合单条命令:
yaml复制scripts:
clean: flutter clean
pubget: flutter pub get
analyze: flutter analyze
但实际项目里,一个步骤往往要执行多条命令。比如“清理并重新拉取依赖”这个动作,用 shell 的逻辑就是先 clean 再 pub get。在 derry 里,可以直接把脚本的值写成 YAML 数组,命令会按顺序依次执行:
yaml复制scripts:
refresh:
- flutter clean
- flutter pub get
derry 执行到某一步失败的时候,会中断后续命令并返回非零退出码。这个特性非常重要,因为你在 CI 里跑脚本时,如果某个子命令失败了,整个任务就应该标记为失败,而不是继续往下跑,否则很容易带着错误产物往下走。
还有一个我很常用的能力是脚本间调用。比如我有一个 clean 脚本,还有一个 build:hap 脚本,我想在 build 之前自动先 clean,就可以这样写:
yaml复制scripts:
clean: flutter clean
build:hap:
- derry run clean
- flutter build hap --release
这样 derry build:hap 执行的时候会先触发 derry run clean,再开始正式构建。这种方式比把 clean 命令复制粘贴到每个脚本里好维护得多,你只需要改一处。
3.2 用变量和参数让脚本活起来
纯字符串命令是死的,真正让脚本“活”起来的是变量和参数。在 derry 里可以通过环境变量来注入动态内容,比如版本号、构建模式、API 地址等。举个实际例子:
yaml复制scripts:
build:dev:
- flutter build hap --debug --dart-define=API_BASE_URL=$API_BASE_URL --dart-define=BUILD_ENV=dev
build:release:
- flutter build hap --release --dart-define=API_BASE_URL=$API_BASE_URL_RELEASE --dart-define=BUILD_ENV=release
执行的时候,外部环境变量 API_BASE_URL 和 API_BASE_URL_RELEASE 会被自动替换进命令里。这样同一个配置文件,在本地开发、测试环境、预发环境都可以复用,只是设置不同的环境变量而已。
derry 还可以通过 derry <script> -- <args> 的方式把参数传给脚本内的命令。比如我可以定义一个通用构建脚本:
yaml复制scripts:
build:
- flutter build hap --$1
执行 derry build --debug,实际跑的就是 flutter build hap --debug。这种方式适合把几个高度相似的构建命令浓缩成一个脚本,减少配置重复。不过有一点要注意,参数解析在 Windows 的 cmd 和 PowerShell 下表现有差异,如果团队里 Windows 用户多,建议在 README 里注明推荐用 PowerShell 或者 Git Bash 执行。
3.3 工作流脚本设计:一次搞定构建、检查、测试
脚本控制台的核心价值在于把流程串起来。我在项目里设计了一套层级的脚本体系,分为“基础命令”“组合命令”“入口命令”三层。
基础命令是原子操作,比如:
yaml复制scripts:
clean: flutter clean
pubget: flutter pub get
analyze: flutter analyze
test:unit: flutter test test/unit/
test:integration: flutter test integration_test/
组合命令把多个基础命令串起来,比如“提交代码前检查”:
yaml复制scripts:
check:beforecommit:
- derry run analyze
- derry run test:unit
- derry run test:integration
入口命令是平时开发最常用的,比如“一键全量构建”:
yaml复制scripts:
build:all:
- derry run clean
- derry run pubget
- derry run analyze
- derry run test:unit
- flutter build hap --release
这套体系的好处是,每个团队成员只需要记住三四个高频入口命令,比如 derry check:beforecommit 和 derry build:all,剩下那些琐碎的细节交给配置文件去兜底。新人上手项目的门槛降低了一大截,不用再翻 README 去找命令了。
4. 鸿蒙项目工作流加速引擎实战
4.1 一套可直接抄作业的构建流程配置
下面这个配置是我目前在 OpenHarmony 项目里实际在用的,你可以直接复制到自己的 derry.yaml 里改改参数就能用:
yaml复制scripts:
# ========= 基础命令 =========
clean: flutter clean
pubget: flutter pub get
analyze: flutter analyze
test:all: flutter test
# ========= HAP 构建 =========
hap:debug:
- flutter build hap --debug --target-platform ohos-arm64
hap:release:
- flutter build hap --release --target-platform ohos-arm64
# ========= 签名 =========
sign:debug:
- hap-sign-tool sign-app -keyAlias debug -signAlg SHA256withECDSA -mode localSign -signerPrivateKeyFile private.pem -signerCertFile cert.pem -inputFile build/xxx-debug.hap -outputFile build/xxx-debug-signed.hap
# ========= 一键流程 =========
build:hap:debug:
- derry run clean
- derry run pubget
- derry run hap:debug
- derry run sign:debug
build:hap:release:
- derry run clean
- derry run pubget
- derry run hap:release
- derry run sign:release
有几点需要特别说明。第一,--target-platform ohos-arm64 这个参数不是所有 Flutter for OpenHarmony 版本都支持,建议你先跑一次 flutter build hap --help 确认实际支持哪些参数。第二,签名命令 hap-sign-tool 在 OpenHarmony 工具链里负责给 HAP 包签名,不同版本的 SDK 命令参数不一样,这里只是一个典型的命令行形式,实际使用时以你的 SDK 文档为准。第三,我故意把 debug 和 release 分成两个脚本,是因为这两个模式下很多参数不同,硬塞进一个脚本反而让配置更复杂。
4.2 多模块工程的脚本编排思路
如果你的 OpenHarmony Flutter 工程也是多模块结构,脚本编排上要格外注意模块间的构建顺序。比如我的工程里有 core、device、ui 三个模块,ui 依赖 device,device 依赖 core。如果直接 flutter build hap 去构建整个项目,依赖关系 Flutter 会自己处理,但如果你需要分别构建不同模块的产物,顺序就非常重要了。
我的做法是在 derry 里把模块构建做成显式的脚本链:
yaml复制scripts:
build:module:core:
- cd modules/core
- flutter build hap --release
- cd ../..
build:module:device:
- derry run build:module:core
- cd modules/device
- flutter build hap --release
- cd ../..
build:module:ui:
- derry run build:module:device
- cd modules/ui
- flutter build hap --release
- cd ../..
注意这里 cd 命令在 Windows 的 cmd 里也支持,但如果你用 PowerShell,路径分隔符可能要调整。为了避免这类跨平台问题,我更推荐的做法是在 flutter 命令里直接指定目标目录,或者用 Dart 写一个统一的构建工具脚本,在 derry 里只做调用。比如在 tool/build_module.dart 里写一个命令行工具,然后 derry 脚本变成:
yaml复制scripts:
build:module:core:
- dart run tool/build_module.dart --module=core
这样就把路径切换逻辑收敛到 Dart 代码里去处理,跨平台问题由 Dart 的 Directory.current 和 Platform.pathSeparator 解决,比在 YAML 里拼 shell 命令要稳得多。
4.3 把 derry 融入 CI/CD 流水线
在本地开发用 derry 只是第一步,真正体现“工作流加速引擎”价值的地方在 CI/CD。开源鸿蒙项目的构建通常要跑在 Linux 的 CI 机器上,而 CI 最害怕的就是流程不透明、命令散落各处。有了 derry 之后,CI 流水线配置会变得非常简洁。
比如在流水线的构建步骤里,只需要执行:
bash复制dart pub global activate derry
derry check:beforecommit
derry build:hap:release
这三条命令就把代码检查、单元测试、集成测试、HAP 构建全跑完了。如果某个环节失败,derry 的非零退出码会立刻让流水线失败,并定位到具体步骤。相比在一堆脚本文件里 grep 报错信息,效率提升是肉眼可见的。
我还建议在 CI 里把产物路径和版本号通过环境变量注入 derry 脚本。比如在流水线里设置:
bash复制export BUILD_NUMBER=20240516.1
export OUTPUT_DIR=./dist
然后在 derry.yaml 里读取这些变量,执行完构建后自动拷贝产物。这样每次构建的产物目录、包名、版本号都清晰可控,不会出现本地打包产物和 CI 打包产物分不清的情况。
4.4 提升工作流效率的冷门小技巧
分享几个我在实际使用中摸索出来的小技巧。
第一个是合理利用 tool 目录写 Dart 脚本。很多操作在 YAML 里写起来很别扭,比如解析 JSON、批量改文件名、检查远端版本。这时候直接在 tool/ 目录下写一个 Dart 脚本,然后用 derry 统一调度,是最舒服的。derry 本身不限制你执行什么语言,只要命令能跑就行。
第二个是在脚本里加日志标记。我会在关键的脚本步骤里加上一些输出标记,比如:
yaml复制scripts:
build:hap:release:
- echo "========== [STEP 1/4] clean =========="
- flutter clean
- echo "========== [STEP 2/4] pub get =========="
- flutter pub get
- echo "========== [STEP 3/4] build hap =========="
- flutter build hap --release
- echo "========== [STEP 4/4] build finished =========="
这样跑长任务的时候,哪怕不盯着屏幕,回头翻日志也能一眼看出跑到哪一步了,排查问题快很多。
第三个是把 derry info 用起来。当你长时间不维护某个项目,忘了脚本具体做了什么,直接敲 derry info <script-name> 就能看到这个脚本对应的完整命令内容,不需要打开 YAML 文件翻,这个细节非常友好。
5. 常见问题与排查技巧实录
5.1 脚本执行失败的排查套路
用 derry 的过程中,我遇到最多的就是“脚本执行失败”的问题。很多新手一看到红色报错就慌了,其实排查思路非常简单:先看报错是哪一层抛出来的。derry 本身解析 YAML 出错的话,通常会在终端直接提示 YAML 语法有问题,比如缩进不对、键名重复。这种情况检查 derry.yaml 的格式就行。
如果 derry 已经成功解析,但脚本里的命令执行失败,这时要看的是命令本身。比如 flutter build hap 报错,那大概率是 Flutter for OpenHarmony 工具链的问题,跟 derry 没有直接关系。这时候我的习惯是先把 derry 里的那条命令复制出来,在终端手动执行一遍,看能不能复现。如果能复现,说明是环境或命令参数问题;如果手动执行能过但 derry 跑不过,那就要怀疑环境变量了,因为 derry 执行的环境和你当前终端的环境可能有差异。
5.2 环境变量与路径的坑
环境变量是 derry 使用中最大的坑,没有之一。derry 执行的命令是从一个不一定继承你当前 shell 环境的上下文里启动的,尤其当你从 IDE 的终端或者 CI 工具里调用 derry 时,有些环境变量可能没传进来。
举个例子,我在 build:release 脚本里用了 $API_BASE_URL_RELEASE,本地终端跑得好好的,推到 CI 上就变成空值了,构建出来的包 API 地址全错了。排查了半天才发现,CI 机器的环境变量没有配置这个值。所以我建议,所有需要在脚本里使用的变量,要么在配置文件里给默认值,要么在 CI 里显式 export。比如:
yaml复制scripts:
build:release:
- flutter build hap --release --dart-define=API_BASE_URL=${API_BASE_URL_RELEASE:-https://api.example.com}
这样即使环境变量没设置,也会用一个默认值兜底,不会直接把空串传进去。
另一个坑是路径。Windows 系统下,YAML 里写 cd .. 这类命令一般没问题,但如果命令里包含具体的路径字符串,比如 D:\workspace\project,反斜杠在 YAML 和命令行里都容易出幺蛾子。我的解决办法是:路径统一用正斜杠,Dart 和 Flutter 工具链都能正常处理正斜杠路径,没必要非得用反斜杠。如果涉及到非常复杂的路径拼接逻辑,还是老实写 Dart 工具脚本吧。
5.3 与 OpenHarmony SDK 交互的避坑指南
用 derry 调度 OpenHarmony 构建任务时,有几个和 SDK 交互的坑非常典型。
第一,flutter build hap 执行的时候,内部会调用 OpenHarmony SDK 里的编译工具,对 JAVA_HOME、OHOS_SDK_HOME 等环境变量相当敏感。如果这些变量没有正确设置,构建会以各种奇怪的方式失败。建议在 derry 的入口脚本里加一步环境检查:
yaml复制scripts:
check:env:
- flutter doctor
- echo "JAVA_HOME=$JAVA_HOME"
- echo "OHOS_SDK_HOME=$OHOS_SDK_HOME"
先跑一次 derry check:env,把输出贴给团队,大家环境一致了再谈构建。
第二,签名命令的调用时机。很多 HAP 包必须在签名后才能安装到真机,但签名又必须在构建完成之后。如果你把签名命令直接拼在 flutter build hap 后面用 && 连接,有时候会因为构建产物路径还没生成完毕而失败。稳妥的做法是在 derry 脚本里拆成两个步骤,构建一个脚本、签名一个脚本,再用组合命令把它们串起来,如上文 build:hap:debug 那样。这样每步都有明确的状态输出,失败了也能定位。
第三,OpenHarmony 的设备连接和安装。在真机上调试时,我们要用工具把 HAP 包装到设备上。这个操作也可以纳入 derry 管理:
yaml复制scripts:
install:debug:
- hdc list targets
- hdc install build/xxx-debug-signed.hap
hdc 是 OpenHarmony 的设备连接工具,类似 Android 的 adb。把它纳入 derry 之后,整个“构建 → 签名 → 安装 → 调试”链路就完全闭环了,再也不用在多个终端窗口之间来回切换。
6. 最后分享一点我的实际体会
从最开始手动敲 17 种命令,到后来所有操作都汇总成 derry build:all 一条命令,这个变化对我个人和团队效率的提升都是巨大的。尤其是新同学入职,不需要再死记硬背项目构建流程,只要知道“跑全量检查敲 derry check:beforecommit,打发布包敲 derry build:hap:release”,就能在几分钟内上手日常开发。
derry 虽然是个很小的工具,但它在工程化链路里的位置非常关键。它帮我们把那些琐碎、易错、重复的命令统一收敛到一个可读、可维护、可版本追踪的配置文件里,让工作流变得透明。后续我还在计划把更多能力接入 derry,比如自动生成 changelog、版本号统一管理、多设备并行安装等。如果你也在做 Flutter for OpenHarmony 相关的项目,强烈建议花半小时把 derry 接进来,你会发现那些每天重复的构建动作,原来可以这么清爽。
