工具链适配鸿蒙:我用 Rust 给 OpenHarmony 造了一条轻量命令行工具链(ark-cli)

前言

如果你做鸿蒙开发,多半在 DevEco Studio 里点过无数次"运行",也多半在 CI 服务器上为"装环境、连设备、搞签名"折腾到崩溃。DevEco 很强,但它把一切都收进了 GUI——一旦要在脚本、CI、远程服务器上批量操作,就会发现鸿蒙侧的命令行几乎是空白。

我做了 ark-cli:一个用 Rust 写的 OpenHarmony 工具链,把 DevEco 的核心能力(装运行时、连设备、构建、跑模拟器、自动签名)搬进一个单二进制命令行工具。这篇文章记录我"适配鸿蒙"的全过程:为什么是 Rust、架构怎么分、运行时怎么一键装齐、hdc 怎么零安装内嵌、hvigor 怎么被 Rust 编排、签名怎么自动化,以及踩过的坑。

先看一眼它长什么样:
在这里插入图片描述

本文所有命令截图均来自真实运行环境(macOS + OpenHarmony SDK API 24),可复现。

快速上手:5 分钟跑通

赶时间的话,下面这条最短路径能让你从 0 到"构建出一个 .hap":

  1. 安装 ark(单二进制,放进 PATH)。
  2. ark runtime install 一键装齐工具链。
  3. ark create Demo --bundle com.example.demo 生成工程。
  4. cd Demo && ark build 构建出包。
# 最短路径:从 0 到 .hap
ark runtime install                          # 装工具链(首次较慢)
ark create Demo --bundle com.example.demo    # 生成工程
cd Demo
ark build --mode debug                       # 产出 entry-default-unsigned.hap
ark devices                                  # 看有没有设备可装
ark run                                      # 有设备就装 + 启动

下文所有命令都围绕"装运行时 → 建工程 → 构建 → 跑设备"这条主线展开,逐段深入。

一、为什么要做一个鸿蒙命令行工具链

1.1 DevEco 很强,但入口太"GUI"

DevEco Studio 是华为官方的鸿蒙 IDE,能力完整——编辑器、预览器、构建、设备管理一应俱全。但它的操作模型是图形化的:你点点点。这在桌面交互时很舒服,却带来三个"脚本化"难题:

  1. 进不了 CI:CI 服务器没有桌面、没有 GUI,DevEco 的很多能力(尤其签名、模拟器)根本触发不了。
  2. 不可复现:手动点出来的环境,换台机器就得重新配一遍,配置散落在各处的 settings 里。
  3. 不可批量:一次性对 10 台设备装 10 个不同的包,GUI 里只能重复劳动。

1.2 命令行是工具链的"操作系统接口"

一个成熟的生态,命令行工具链往往是它的"操作系统接口":脚本调它、CI 调它、其他工具也调它。Android 有 adb、gradle、avdmanager;iOS 有 xcodebuild、xcrun。而鸿蒙侧,长期以来这些能力主要绑在 DevEco 里,散落的 hdc/hvigor 又各自为政,缺少一个统一、可脚本化、可分发的入口。

1.3 一个能脚本化的 CLI 能带来什么

场景传统方式(GUI / 散命令)用 ark-cli
新机器装环境手动下 SDK + 配路径 + 装 JDKark runtime install 一条命令
CI 构建出包无 GUI,构建链断裂ark build 纯命令行
连设备看日志找 hdc 路径 + 手动传参ark devices / ark logcat -m Screen
起模拟器DevEco 图形界面ark emulator create / start
生成签名DevEco 登录 + 点按钮 + 复制材料ark signature generate

核心目标不是"再造一个 DevEco",而是把 DevEco 里可以命令化的那部分抽出来,做成一个稳定、可组合、单二进制分发的 CLI。

二、为什么选 Rust 做这条工具链

选语言时我纠结过 Go(更简单、生态熟)和 Rust(更重、但更强)。最终选 Rust,理由有三条,条条都踩在这条工具链的"要害"上。

2.1 单二进制分发:CI 友好的前提

这条工具链要在 CI 上用,"下载即用、零依赖"是硬指标。Rust 编译出的是单个静态二进制,不依赖运行时、不拖一堆 .so/.dll。对比一下:

维度RustGo
产物单二进制,静态链接单二进制,静态链接
内存安全编译期所有权保证依赖 GC
二进制解析 / 加密无 GC 停顿,行为确定GC 停顿不可控
交叉编译cargo build --target 成熟同样成熟
学习曲线陡平缓

2.2 内存安全是"碰密钥"的底线

工具链里有大量解析二进制、证书、密钥的活儿:读 .hap、处理 .p7b/.cer、加密签名密钥。这些代码一旦有内存漏洞,后果是签名材料泄露或产物被污染。Rust 的所有权系统在编译期就排除了 use-after-free、data race 这类问题——对"天天碰密钥"的工具链,这是硬需求,而不是锦上添花。

2.3 一套代码,三端编译

鸿蒙工具链要跑在 macOS(研发主力)、Windows、Linux(CI) 上。Rust 用 #[cfg(target_os)] + 条件依赖就能一套代码三端编译:

# 一条命令交叉编译到各平台
cargo build --release --target aarch64-apple-darwin     # macOS (Apple Silicon)
cargo build --release --target x86_64-pc-windows-msvc   # Windows
cargo build --release --target aarch64-unknown-linux-gnu # Linux (ARM / CI)

选 Rust 的代价是学习曲线陡,但对"要长期维护、要碰密钥、要进 CI"的工具链,这笔账很划算。

三、ark-cli 的整体架构

3.1 一个 workspace,三个 crate

ark-cli 是一个 Cargo workspace,逻辑按"能力"拆成三个 crate,避免"一个大 main 撑死":

ark-cli/
├── Cargo.toml                 # workspace 根
├── crates/
│   ├── core/                  # 全部领域逻辑:运行时 / hdc / hvigor / 签名 / 模拟器
│   ├── cli/                   # 命令行入口:clap 子命令 → 调 core
│   └── gui/                   # Tauri 桌面入口:同一个 core,另一张脸
└── scripts/                   # 发布 / 内嵌二进制构建脚本

核心设计:逻辑沉 core,入口保持薄。 cli 和 gui 都只做"参数解析 + 调 core + 打印结果",所有真正的活儿都在 core。这样同一套能力,命令行能用、桌面 GUI 也能用;将来再加 MCP 工具、加插件,都只是"再加一个薄入口"。

3.2 一个核心,两个入口

              ┌────────────────┐
              │      core      │   运行时 / hdc / hvigor / 签名 / 模拟器
              └───────┬────────┘
            ┌─────────┴─────────┐
      cli (clap)           gui (Tauri)
        终端 / CI 用户         桌面用户

这种"厚 core + 薄入口"的拆法,是这条工具链能同时服务 CI 脚本和桌面 GUI 的关键——能力只写一遍。

四、统一运行时:一条命令装齐工具链

4.1 痛点:配环境最劝退

鸿蒙开发最劝退的不是写代码,是配环境。SDK 要下、路径要配、JDK 要装、hvigor / ohpm 要找到、hdc 要在 PATH 里……每一步都可能卡住,而且不同 DevEco 版本目录结构还不一样。

ark-cli 的解法是引入"统一运行时"概念:ark runtime install 一键把 SDK、Emulator、hvigorw、ohpm、node、hdc 全部下载并归置到一个受管目录 ~/.ark-cli/runtime/ 下,之后所有命令都从这个目录找工具,不再依赖 DevEco 装在哪儿。

4.2 ark runtime install 做了什么

  1. 探测本机架构(aarch64 / x86_64)和操作系统。
  2. 下载对应平台的 OpenHarmony SDK。
  3. 下载 Emulator 运行时。
  4. 安装 hvigorw / ohpm / node 到 runtime/bin、runtime/ohpm、runtime/tool。
  5. 写出 config.toml,记录每个工具的绝对路径。

装完用 ark runtime status 一键体检,每个组件都会给出 ✅ / ❌:

在这里插入图片描述

# 一键装齐 + 体检
ark runtime install          # 下载并归置整套工具链
ark runtime status           # 逐组件体检(SDK / Emulator / hvigorw / ohpm / node / hdc)
ark runtime list             # 看已安装了哪些组件

4.3 分量级安装:按需下载

运行时各组件互相独立,所以支持分量级安装——只装你需要的那部分:

ark runtime install sdk         # 只装 SDK
ark runtime install emulator    # 只装模拟器
ark runtime install hvigorw     # 只装构建器

设计原则:依赖关系下沉到 core 的路径解析里,命令之间不互相假设"装了什么"——装没装、装在哪,status 说了算。

五、零安装设备交互:把 hdc 内嵌进二进制

5.1 为什么内嵌 hdc

hdc(HarmonyOS Device Connector)是鸿蒙世界的"adb"——连设备、装包、传文件、抓日志全靠它。传统做法是把 SDK 里的 hdc 加进 PATH,但这又回到了"配环境"的老路:路径不对、版本不对、CI 上根本没有。

ark-cli 的做法:把 hdc 二进制直接内嵌进 ark 可执行文件(include_bytes!),首次使用时解压到 ~/.ark-cli/bin/hdc 再执行。用户机器上不需要单独装 hdc,ark 自带。

5.2 100% 透传 + 结构化封装

设备交互我做了两层,兼顾"能力全"和"体验好":

  • 透传层:ark hdc <任意参数> 把参数原样传给内嵌 hdc,hdc 支持什么它就能做什么,一个都不少。
  • 封装层:高频操作做成子命令,带结构化输出(--json 可被脚本直接消费)。

5.3 设备 / 日志 / 端口转发

ark devices                      # 列出在线设备
ark hdc shell "bm dump -a"       # 透传:列出已安装的应用
ark logcat -m Screen             # 抓日志,Screen 模式只刷屏幕相关 tag
ark logcat -m Crash              # 只看崩溃日志
ark forward tcp:8080 tcp:8080    # 端口转发

透传是底线:封装可以慢慢加,但 hdc 的任何能力都不能被 ark 挡住。

六、构建系统:用 Rust 编排 hvigor

6.1 注入环境 + 规避 daemon 陷阱

鸿蒙项目用 hvigor 构建。但直接在命令行调 hvigorw 有两个坑:环境变量不全(缺 DEVECO_SDK_HOME 等),以及 daemon 不继承父进程环境——第一次 build 注入了环境,后台 daemon 却没继承,第二次 run 又找不到 SDK,表现为"时好时坏"。

ark-cli 的 ark build 针对这两点做了处理:

  1. 从统一运行时解析出完整环境变量(SDK 路径、node、JDK),注入到每一次构建子进程。
  2. 强制前台构建(不依赖 daemon 继承环境),根除"第一次成功、第二次失败"的玄学。

6.2 从模板到运行:create + build + run

新项目不用手搓目录结构,ark create 按模板生成一套能直接构建的工程:

在这里插入图片描述

构建则是一条命令,--watch 支持增量热构建:

在这里插入图片描述

ark create MyApp --bundle com.example.myapp   # 按模板生成工程
ark build --mode debug                        # 构建出 .hap
ark build --watch                             # 监听文件变化,增量构建
ark run                                       # 构建 + 安装 + 启动到设备

6.3 产物与签名分离

构建产物默认是未签名的 .hap(entry-default-unsigned.hap)。签名是独立的一步(见下一章)——这样 CI 可以"先构建、后签名",签名材料不会泄漏进构建产物,职责清晰。

关键经验:跨进程"注入环境"不可靠。daemon 是独立的长驻进程,它只认自己启动时的环境。要么每次都前台跑,要么把环境写进 daemon 自己的配置里。

七、模拟器全生命周期管理

7.1 实例线:create / start / stop / delete

DevEco 的模拟器管理藏在图形界面里,命令行几乎是空白。ark-cli 把模拟器的实例做成一组命令:

ark emulator list                 # 看有哪些模拟器
ark emulator create phone01       # 创建实例
ark emulator start phone01        # 启动
ark emulator stop phone01         # 停止
ark emulator delete phone01       # 删除

7.2 镜像线:image-install 带进度下载

模拟器要跑,得有系统镜像。ark-cli 把镜像的下载也命令化,带实时进度:

ark emulator image-list           # 看可下载的镜像
ark emulator image-install phone  # 下载 phone 镜像(带进度条)

ark emulator list 会同时给出 Emulator 版本和当前实例状态:

在这里插入图片描述

设计上把模拟器拆成两条独立线:实例(create / start / stop / delete)管"实例的生命周期",镜像(image-list / image-install)管"系统镜像的来源",两者互不耦合。

八、自动签名:把 DevEco 的按钮变成一条命令

8.1 四种签名材料

鸿蒙应用上架 / 安装需要一整套签名材料,在 DevEco 里是"登录 → 点几个按钮 → 手动复制"。ark-cli 把它做成一条命令,覆盖四种材料:

材料文件用途
密钥库.p12应用私钥,本地保管
调试证书.cer调试期证书
发布证书.cer上架用证书
Profile.p7b把应用 bundle 与证书绑定的描述

8.2 六阶段流程 + 幂等检查

ark signature generate 内部是一条六阶段流水线,每一步都做幂等检查,已存在且正确的材料自动跳过:

  1. 登录态检查:确认 AppGallery Connect 已登录。
  2. 生成 / 复用密钥:.p12 已存在则跳过,否则生成并加密保管。
  3. 申请证书:向 AGC 申请调试 / 发布证书。
  4. 生成 Profile:.p7b,绑定应用 bundle 与证书。
  5. 落地配置:把材料写进 build-profile.json5 的 signingConfigs。
  6. 验证:重新构建,确认这次产出的就是已签名的 .hap。

8.3 Rust 驱动 Java 工具链

鸿蒙签名工具链底层是 Java 的。ark-cli 用 Rust 编排:Rust 负责流程、幂等、错误处理,把重活委托给底层成熟的 Java 工具,再用 Rust 解析结果。这样既复用了现成的 Java 能力,又保证流程可控、可脚本化:

ark login                 # 先登录 AGC
ark signature generate    # 自动生成全套材料并配置到项目
ark build                 # 验证:这次构建产出已签名的 .hap

设计要点:幂等是自动签名的生命线。CI 里这条命令会被反复跑,任何一步"重复执行就报错"都会让流水线变脆。所以每个阶段都先查"有没有、对不对",再决定做不做。

九、踩过的坑(都是真金白银换的)

9.1 macOS 上的"假 java"

macOS 系统里自带一个 java,它能跑、退出码还是 0,但它只是一个"引导桩",并不是一个真正的 JVM——java -version 给的信息不可信。这导致 JDK 检测"看起来成功",可 hvigor 真正构建时却因找不到真 JDK 而失败,而且失败信息还很隐蔽。

解决办法:不能只信退出码,要校验 java 的真实身份(厂商 / 版本 / 路径),把系统桩排除掉,改用运行时自带的 JDK:

// 不能只信退出码:校验 java 的真实身份,排除 macOS 系统桩
fn is_real_jdk(path: &Path) -> bool {
    let out = std::process::Command::new(path)
        .arg("-version")
        .output()
        .ok()?;
    // 注意:java -version 的输出在 stderr,不在 stdout
    let info = String::from_utf8_lossy(&out.stderr);
    info.contains("OpenJDK")
        || info.contains("Temurin")
        || info.contains("Java(TM)")
}

9.2 字节 vs 字符:一个"测试全绿"的下载 bug

下载进度条算"百分比"时,我一度把字节数当成了字符数来算长度。对纯 ASCII 两者相等,测试全绿;可一旦内容含中文(多字节 UTF-8),比例就错了,进度条一度"倒着走"。

// 进度条算比例:数"字符"而不是"字节",否则中文会让比例错乱
let total = name.chars().count();    // ✅ 字符数
// let total = name.len();           // ❌ 字节数(UTF-8 里一个中文字符 = 3 字节)
let ratio = done as f64 / total as f64;

教训:UTF-8 里"一个字符 ≠ 一个字节"。任何涉及"长度 / 进度 / 截断"的地方,先问清楚自己数的是 len() 还是 chars().count()。

9.3 Windows 中文路径的 GBK 陷阱

Windows 上不少老工具(包括部分 SDK 工具)用 GBK 而非 UTF-8 处理路径。一旦路径里出现中文,子进程就拿到乱码、找不到文件。修复方式是在跨平台边界上显式声明编码,绝不依赖系统默认。

适配鸿蒙这套工具链,我沉淀出六条经验:

  1. 单二进制分发是工具链进 CI 的前提,别让用户装依赖。
  2. 厚 core + 薄入口:能力只写一遍,命令行 / GUI / 插件共享同一套逻辑。
  3. 透传是底线:封装可以有,但不能挡住底层工具的原生能力。
  4. 跨进程注入环境不可靠:要么前台跑,要么写进对方自己的配置。
  5. 幂等是自动化的生命线:CI 会反复跑,每一步都要"先查再做"。
  6. 编码要显式声明:别假设 UTF-8,尤其在 Windows。

十、展望

10.1 覆盖度

  • 补全 Windows 上的签名与模拟器端到端覆盖(目前 macOS 已跑通)。
  • 输出更多 --json 结构化字段,让第三方编排更稳定。
  • 模拟器镜像支持断点续传,弱网环境下载更稳。

10.2 生态

  • 把高频操作沉淀成 MCP 工具,让 AI 助手能直接驱动这条工具链。
  • 面向 CI 提供"一行装环境 + 一行出包"的最小工作流模板。
  • 与社区共建,把适配经验沉淀成可复用的最佳实践。

附录:常见问题

Q1:ark-cli 会和 DevEco 冲突吗?

不冲突。ark-cli 只是去调用 DevEco / SDK 里的工具(hvigor、hdc、签名工具),不修改 IDE 本身,两者可以并存,甚至同时用。

Q2:没装 DevEco 能用吗?

能。ark runtime install 会把 SDK 和工具链独立装到 ~/.ark-cli/runtime/ 下,不依赖 DevEco 是否安装。这也是它能进 CI 的原因。

Q3:支持 Linux CI 吗?

支持。Rust 交叉编译到 aarch64-unknown-linux-gnu,产出的单二进制直接就能跑,不需要在 CI 上再装任何依赖。

Q4:签名密钥安全吗?

.p12 私钥在本地加密保管;ark signature generate 是幂等的,重复跑不会重新生成、也不会把密钥写进构建产物或日志。

总结

ark-cli 想证明一件事:鸿蒙的工具链,可以像 Android 的 adb / gradle 一样,被命令、被脚本、被 CI 稳稳地驱动——而不必每次都打开一个 GUI。从 Rust 单二进制、统一运行时、内嵌 hdc,到编排 hvigor、自动化签名,每一步都是"把 DevEco 里能命令化的部分抽出来"。它不取代 DevEco,而是补上 DevEco 够不到的那一片——脚本、CI、批量、远程。

下面是全部命令的速查表:

命令速查表

命令作用
ark runtime install一键装齐 SDK / Emulator / hvigorw / ohpm / node / hdc
ark runtime install <组件>分量级安装(sdk / emulator / hvigorw)
ark runtime status逐组件体检,给出 ✅ / ❌
ark devices列出在线设备
ark hdc <参数>100% 透传 hdc
ark logcat -m <模式>抓日志(Screen / Crash 等模式)
ark forward <local> <remote>端口转发
ark create <名> --bundle <b>按模板生成可构建工程
ark build [--mode debug]构建出 .hap
ark build --watch监听文件变化,增量热构建
ark run构建 + 安装 + 启动到设备
ark emulator list列出模拟器与版本
ark emulator create/start/stop/delete模拟器实例生命周期
ark emulator image-list查看可下载的系统镜像
ark emulator image-install <设备>下载系统镜像(带进度)
ark login登录 AppGallery Connect
ark signature generate自动生成签名材料并配置到项目

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐