在鸿蒙 PC 上用 Rust + Tauri 开发鸿蒙应用:从零到真机跑通

0 评论 63 浏览 0 收藏 91 分钟

在华为 MateBook Pro 的 HarmonyOS 7.0 上,用 Tauri 的 OpenHarmony 分支把 Rust 应用跑进原生窗口:外层 ArkTS 壳加载 N-API 模块,内层 wry 起 ArkWeb。命令与报错都来自真机。

验证环境:HUAWEI MateBook Pro(HAD-W32),HarmonyOS 7.0.0.111 / OpenHarmony-7.0.0.109 / API 26,aarch64(2in1)。文中所有命令、报错原文、体积与耗时都来自这台真机的实测记录,不是从别的平台推断出来的流程。

用到的框架:tauri-apps/tauri 的 feat/open-harmony 分支(参考工程 richerfu/tauri-demo)。Rust 工具链是本机原生的 aarch64-unknown-linux-ohos host。

1. 先说结论:能跑到什么程度

最终形态是一个真正意义上的 Tauri 应用,跑在鸿蒙 PC 的原生窗口里:

  • 外层是 ArkTS 壳:入口 Ability 继承 @ohos-rs/ability 提供的 NativeAbility,由它把 Rust 动态库作为 N-API 模块加载进应用进程;
  • 内层是 Rust:tauri + wry 的 ohos 后端起了一个 ArkWeb(Chromium)webview,前端页面由 Tauri 的资源协议提供;
  • 前端是零依赖静态页面:用 withGlobalTauri 暴露的全局对象直连 Rust 命令,不需要打包器;
  • 前后端双向都通:前端按钮触发 app_info / greet 命令,返回值回填 DOM;同时保留了 Rust → ArkTS 的 N-API 回调(含 TSFN 异步线程)能力。

界面上第 1 张卡片返回的真实数据(这是从设备上抓的界面节点文本):

{

“product”: “harmony-tauri-app”,

“version”: “0.1.0”,

“os”: “linux”,

“arch”: “aarch64”,

“family”: “unix”,

“ohos”: true,

“ohos_library”: “tauri_demo_lib”,

“ohos_package”: “com.atomcode.harmonytauri”

}

第 2 张卡片返回 Hello, 鸿蒙开发者!You’ve been greeted from Rust!。

两个细节值得注意:

  1. “ohos”: true 来自编译期常量 cfg!(target_env = “ohos”),这是我特意加的判据;
  2. “os”: “linux” 是 Rust std::env::consts::OS 在 ohos 目标下的正常取值 —— ohos 目标的 target_os 是 linux、target_env 才是 ohos。想判断”是否鸿蒙”必须看 target_env,看 target_os 会误判。

版本与体积记录(便于你和自己的结果对照):

项目 实测值
Rust / cargo 1.99.0(harmonybrew 构建,host = aarch64-unknown-linux-ohos)
tauri / wry / tao 2.8.5 / 0.55.1 / 0.35.3(均来自 ohos 分支)
openharmony-ability 0.3.0(rev 295a276a)
@ohos-rs/ability

(ohpm 包)

0.4.0-beta.5
DevEco SDK API 26(26.0.0.105)
hvigor / ohpm / hdc 7.26.8 / 6.0.1 / 3.2.0f
Rust 动态库体积 10.7 MB → 9.4 MB(加上 custom-protocol 之后,见第 7 章)
签名后的 HAP 8.6 MB
全量 Rust release 构建 2 分 02 秒(20 核 aarch64)
增量重编译 30 ~ 44 秒
hvigor 组装 HAP 6 ~ 12 秒

2. 为什么鸿蒙 PC 上这套流程处处别扭

如果你只做过 Android/iOS 上的 Tauri,会有一段明显的“水土不服”。原因是鸿蒙 PC 的用户态和普通 Linux 发行版有四个硬性差别,它们分别对应后文的四个大坑。

2.1 用户态是 musl,且目标三元组是 ohos 专用

$ ls -l /lib/ld-musl-aarch64.so.1 /lib/ld-linux-aarch64.so.1

-rwxr-xr-x 1 root shell 2223008 2024-06-06 08:00 /lib/ld-musl-aarch64.so.1

ls: /lib/ld-linux-aarch64.so.1: No such file or directory

没有 glibc。目标三元组必须是 **aarch64-unknown-linux-ohos**,不是 aarch64-unknown-linux-musl,更不是任何 gnu。好在 Rust 官方把 aarch64-unknown-linux-ohos 列在 Tier 2 with Host Tools,也就是说上游会为本机发布 rustc/cargo/clippy/rustfmt 全套构件——这是整件事能成立的前提。

2.2 系统里没有 C 工具链

$ for c in rustc cargo gcc cc clang make cmake ninja python3; do printf ‘%s: ‘ “$c”; command -v “$c” || echo “(missing)”; done

rustc: (missing)

cargo: (missing)

gcc: (missing)

cc: (missing)

clang: (missing)

make: (missing)

cmake: (missing)

ninja: (missing)

python3: (missing)

这不是“没装”,是 Toybox 用户态本身就不带。后果:链接必须有链接器,而 Rust 默认调 cc;交叉编译的 C 代码也要有编译器。好在本机随 HarmonyOS Command Line Tools 装好的 OpenHarmony SDK 里带了一套能在设备上原生跑的 LLVM,可以直接借用(第 6.4 节)。

2.3 二进制必须自签名,否则内核拒绝执行

这是最容易忽略、也最致命的一条。任何未经签名的 ELF 在本机都无法执行:

$ ./hello

sh: ./hello: Operation not permitted

$ echo $?

126

而且限制与路径无关(~/.harmonybrew/Cellar、~/.local/bin 都试过,一样 126),只和文件内容里有没有合法签名有关。更细的观测是:报错会分两种,取决于文件里有没有 .codesign 段。

文件状态 报错 说明
完全没有 .codesign 段(例如拿别处编好的产物) Permission denied

(126)

只有 .note.ohos.ident 段
有 .codesign 段但内容是占位符(lld 默认行为,20 KB 全零) Operation not permitted

(126)

段在、签名不合法
.codesign

段是 4 KB 的真实自签名

正常执行 —

所以本地工具链的每一个链接产物都要补一步签名。这个坑在后面第 6.4 节会以“链接器包装器”的形式一次性解决。

2.4 文件系统会间歇性返回 EINTR

这台机器的文件系统(应用沙箱 / HNP 挂载之上)会偶发让系统调用返回 EINTR。C 程序通常无害(会自动重试),Rust 程序则可能把它当成致命错误。本文后面有两个坑都是它造成的:

  • cargo 的 git checkout 一撞上 EINTR 就整趟失败(第 6.2 节);
  • 上一篇文章里 brew install 因为 Ruby 的目录枚举遇 EINTR 而必然失败。

记下这条:在这个平台上,“重试”往往比“修配置”更有效,但要分清楚哪些工具会自己重试、哪些不会。

2.5 一句话总结差异

维度 鸿蒙 PC 实际情况 对 Tauri 开发的影响
libc musl,目标三元组 …linux-ohos 依赖里所有预编译产物都得换
C 工具链 无 cc/gcc/clang/make 必须指定链接器;C 依赖要走 SDK 的 clang
可执行文件 未签名一律拒执行 链接后必须签名,且要自动化
文件系统 偶发 EINTR cargo/git/hvigor 都可能莫名失败
应用签名 HarmonyOS 设备不认 OpenHarmony 根证书 必须走 AGC 申请证书与 profile

3. Tauri 在鸿蒙上是怎么跑起来的

先建立正确的心理模型,否则后面各种配置项会显得很随机。

3.1 分层结构

┌──────────────────────────────────────────────────────────┐

│ 鸿蒙应用(HAP,已签名)                                    │

│                                                          │

│  ArkTS 层                                                │

│   EntryAbility extends NativeAbility(@ohos-rs/ability)  │

│     moduleName = “tauri_demo_lib”                        │

│     import rustNapi from ‘libtauri_demo_lib.so’          │

│     └─ 注册 ArkTS 回调,供 Rust 反向调用                    │

│                                                          │

│  原生库层(libs/arm64-v8a/)                              │

│   libtauri_demo_lib.so   ← 我们编译的 Rust cdylib         │

│     ├─ napi-ohos         N-API 绑定(Rust ↔ ArkTS)      │

│     ├─ tauri 2.8.5       应用框架、IPC、资源协议           │

│     └─ wry 0.55.1        ohos 后端的 webview(ArkWeb)     │

│   libc++_shared.so / libentry.so(模板遗留,可忽略)        │

│                                                          │

│  Web 层(ArkWeb / Chromium)                              │

│   index.html + main.js + style.css                       │

│     window.__TAURI__.core.invoke(“greet”, {name})        │

└──────────────────────────────────────────────────────────┘

关键点:鸿蒙上没有“Tauri 应用”这种进程,只有一个带 webview 的鸿蒙应用,Rust 是被加载进它进程里的动态库。 这与 Android 上的 Tauri 是同构的(Android 是 TauriActivity + lib<app>.so),只是这里的 Ability 由 ohos-rs 社区提供。

3.2 一次按钮点击的完整调用链

以第 1 章那个“打招呼”按钮为例:

  1. webview 里的 main.js 执行 window.__TAURI__.core.invoke(“greet”, { name: “鸿蒙开发者” });
  2. Tauri 的 IPC 把请求交给 Rust 侧注册的命令处理函数(本工程里是 src-tauri/src/lib.rs 的 #[tauri::command] fn greet(&str) -> String);
  3. Rust 返回字符串,经 IPC 回到 webview;
  4. main.js 把结果写进 DOM,界面上出现 Hello, 鸿蒙开发者! …。

反向(Rust → ArkTS)走的是另一条路:napi-ohos 把 ArkTS 传进来的回调函数包成 FunctionRef / ThreadsafeFunction,Cargo 侧用 #[napi] 导出注册函数,EntryAbility 在 onCreate 里调用它们完成注册。本工程的 lib.rs 里那套 register_main_thread_arkts_callback / register_async_thread_arkts_callback / register_main_thread_dispatcher 就是这条链路的三个样例(主线程函数调用、异步线程 TSFN、非阻塞派发)。

3.3 工程文件布局(鸿蒙侧)

Tauri 的移动端工程有个“生成目录”约定,鸿蒙侧是 src-tauri/gen/ohos,等同 Android 的 src-tauri/gen/android:

路径 作用 会不会被重新生成
gen/ohos/AppScope/app.json5 包名 bundleName、应用名、版本号 手工维护
gen/ohos/build-profile.json5 产品定义、signingConfigs、兼容 SDK 版本 签名由 devecocli 写入
gen/ohos/entry/src/main/module.json5 模块名、入口 Ability、设备类型、权限 手工维护
gen/ohos/entry/src/main/ets/entryability/EntryAbility.ets ArkTS 壳,声明 Rust 库名与回调 手工维护
gen/ohos/entry/src/main/ets/pages/Index.ets 兜底页面(webview 才是主界面) 模板生成
gen/ohos/entry/src/main/cpp/ 模板自带的 libentry.so(N-API 示例) 可以忽略
gen/ohos/entry/src/main/cpp/types/libtauri_demo_lib/ 给 ArkTS 的 .d.ts 类型声明 与 Rust 导出保持一致
gen/ohos/entry/libs/arm64-v8a/libtauri_demo_lib.so 我们编译出来的 Rust 库 每次构建后放进来
gen/ohos/assets/tauri.conf.json 运行时真正读的配置

(第 7 章重点)

必须与源码里的配置同步

EntryAbility.ets 里最关键的三行:

import { NativeAbility } from ‘@ohos-rs/ability’

import rustNapi from ‘libtauri_demo_lib.so’;

export default class EntryAbility extends NativeAbility {

public moduleName: string = “tauri_demo_lib”   // 不带 lib 前缀、不带 .so

public defaultPage: boolean = true;

moduleName 与 Cargo.toml 里 [lib] name、以及 libs/arm64-v8a/ 下的文件名必须三者一致,否则加载不到。

3.4 前端资源到底存在哪

这是最容易搞错的一点,涉及两个地方:

位置 谁写 谁读 何时生效
Rust 库内嵌的资源(tauri::generate_context! 展开时把 frontendDist 目录编进二进制) tauri-build

/ tauri-codegen 在编译期

运行期的资源协议处理器 编译时确定
HAP 内 assets/tauri.conf.json tauri-cli 的 inject_resources 在每次构建时注入 运行期的 AppManager 运行时读取

也就是说:**改了 src-tauri/tauri.conf.json,必须同时刷新 gen/ohos/assets/tauri.conf.json**,否则运行时读到的还是旧配置。我第一次翻车就翻在这里(第 7 章会看到那条经典报错)。

4. 环境准备

4.1 需要什么

组件 本机来源 版本 用途
hdc hmos-clt(软链到 ~/.local/bin/hdc) 3.2.0f 连设备、装 HAP、看日志
hvigorw hmos-clt 7.26.8 组装 HAP
ohpm hmos-clt 6.0.1 拉 ArkTS 依赖(@ohos-rs/ability)
DevEco SDK hmos-clt($DEVECO_SDK_HOME) API 26 / 26.0.0.105 ArkTS 编译器、Clang/LLD、打包与签名工具
Rust 工具链 harmonybrew 的 rust 包 1.99.0,host=aarch64-unknown-linux-ohos 编译 Rust 侧
Node.js harmonybrew 的 node v26.12.0 ohpm / hvigorw / devecocli 的运行基础
cmake + ninja harmonybrew(或用 SDK 自带的) 4.4.4 / 1.13.2 hvigor 编译模板里的 C++ 库
devecocli npm 全局 @deveco/deveco-cli 1.3.6 AGC 签名材料 + 一键构建安装运行

配置 SDK 相关环境变量(写进 ~/.zshrc 更省事):

export HMOS_CLT_HOME=/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0

export DEVECO_SDK_HOME=”$HMOS_CLT_HOME/sdk”

export PATH=”$HMOS_CLT_HOME/ohpm/bin:$HMOS_CLT_HOME/hvigor/bin:$HMOS_CLT_HOME/sdk/default/openharmony/toolchains:$PATH”

4.2 本机真实遇到的两个环境坑

坑一:坏的 node 会把整套鸿蒙工具链带崩。 本机 ~/.local/bin/node 指向一个 120 MB 的旧二进制,执行直接 Permission denied(未签名/架构不符),而它偏偏在 PATH 最前面:

$ ohpm -v

ERROR: node: …/ohpm/bin/ohpm[7]: /storage/Users/currentUser/.local/bin/node: Permission denied

ERROR: NODE_HOME: …/ohpm/bin/ohpm[12]: /node: inaccessible or not found

hvigorw 同样会中招。修法是把这几个软链指回能用的 node:

for b in node npm npx; do ln -sfn “$HOME/.harmonybrew/bin/$b” “$HOME/.local/bin/$b”; done

坑二:brew install 在这台机器上必然失败(Ruby 目录枚举遇 EINTR 致命退出)。装包一律走 brew fetch <包> + 手工解包装配。这是上一篇文章的主题,这里不展开;本文只需要 rust、cmake、ninja 这几个已经装好的东西。

4.3 设备连接

鸿蒙 PC 上做开发有个便利:idevice 就是本机自己,hdc 通过回环连过来。

$ hdc list targets

127.0.0.1:34953

$ devecocli device list

Name                 Serial           Kind    Device Type

HUAWEI MateBook Pro  127.0.0.1:34953  device  2in1

Device Type 是 2in1 —— 这意味着应用要能在 PC 形态下运行,module.json5 的 deviceTypes 必须包含 2in1(模板默认给了 phone/tablet/2in1/tv/car,先不用改)。

4.4 签名材料:两套,别混用

材料 位置 能用在哪
OpenHarmony 自带(OpenHarmony.p12 / OpenHarmonyApplication.pem / UnsgnedDebugProfileTemplate.json) $DEVECO_SDK_HOME/default/openharmony/toolchains/lib/ 只能本地验签

,装不上 HarmonyOS 设备

AGC 申请的(.p12 / .cer / .p7b) ~/.ohos/config/

(由 devecocli 自动生成)

可以装到真机

这一点非常关键,第 8 章会给出那条让人一脸问号的错误码。

5. 工程搭建:为什么我不用 tauri-cli 一条命令

5.1 官方推荐流程长什么样

参考工程 richerfu/tauri-demo 的 README 给的流程是标准 Tauri 移动端套路:

# 1) 装 CLI(从 ohos 分支源码编译)

cargo install tauri-cli –git https://github.com/tauri-apps/tauri –branch feat/open-harmony

cargo install ohrs

# 2) 前端依赖

pnpm install

# 3) 配置 SDK 与 PATH

export OHOS_HOME=/path/to/ohos-sdk/darwin

export PATH=/path/to/command-line-tools/bin:$PATH

# 4) 构建(会调用构建器、打包 HAP)

cd src-tauri

OHOS_HOME=$OHOS_HOME cargo tauri ohos build \

–config “{\”build\”:{\”beforeBuildCommand\”:\”pnpm –dir ‘${ROOT}’ build\”}}”

如果 CLI 卡在打包环节,README 还给了兜底:直接进 src-tauri/gen/ohos 手搓 ohpm install && hvigorw assembleHap,产物是未签名 HAP,签名交给 DevEco Studio 或自己配 signingConfigs。

这条路没错,但在这台机器上它有三个现实问题:

  1. cargo install tauri-cli 太贵:它要编译整个 tauri-cli 依赖树(数百个 crate)。而本机全量编译一次应用侧的 tauri 依赖树就已经 2 分 02 秒,CLI 只会更久,且本机文件系统偶发 EINTR,长时间编译中途翻车的概率不低。
  2. 构建期环境变量必须精确控制:tauri-cli 构建时会注入 4 个环境变量(WRY_OHOS_PACKAGE 等,见 6.3 节)并同步注入运行时配置。绕过 CLI 时这些都得自己给,反而更可控。
  3. 前端依赖树在这个平台上很脆:vite 6 依赖 rollup,而 rollup/rolldown、esbuild 这类包在鸿蒙上没有预编译的原生制品,装依赖阶段就会踩坑(这是本机环境的已知问题)。

于是我的选择是:手工构建 + 全部脚本化,并且把前端做成零依赖静态页面。

顺带说清楚:gen/ohos 这个鸿蒙工程目录,官方是由 cargo tauri ohos init 从 CLI 内置模板(crates/tauri-cli/templates/mobile/open-harmony/)生成的。我直接复用了参考 demo 已经生成好的 gen/ohos(两者同源),再改包名与应用名——效果等价于跑过一次 tauri ohos init。

5.2 零依赖前端:为什么值得这么做

Tauri v2 支持 app.withGlobalTauri = true,此时 webview 里会直接挂一个 window.__TAURI__ 全局对象,于是前端可以完全不引入 @tauri-apps/api,也就不需要任何打包器:

// tauri.conf.json

“app”: {

“withGlobalTauri”: true,

“windows”: [

{ “label”: “main”, “url”: “index.html”, “title”: “鸿蒙 Tauri 应用”, “width”: 900, “height”: 640 }

]

}

注意 “url”: “index.html” 这一行,它不是可选的装饰。省掉它,窗口默认会去请求根路径,进而触发第 7 章那个报错。

前端调用后端命令长这样(web/main.js):

// 通过 withGlobalTauri 暴露的全局对象直连 Rust 命令,不需要任何 npm 依赖

const invoke = window.__TAURI__.core.invoke;

asyncfunction call(label, cmd, args) {

try {

log(`→ ${cmd} ${args ? JSON.stringify(args) : “”}`);

const result = await invoke(cmd, args);

log(`← ${JSON.stringify(result)}`, “ok”);

return result;

} catch (error) {

log(`✗ ${label} 失败:${error}`, “err”);

}

}

document.getElementById(“btn-info”).addEventListener(“click”, async () => {

const info = await call(“应用信息”, “app_info”);

document.getElementById(“info-out”).textContent = JSON.stringify(info, null, 2);

});

Rust 侧对应的命令(src-tauri/src/lib.rs):

#[derive(serde::Serialize)]

struct AppInfo {

product: &’staticstr,

version: &’staticstr,

os: &’staticstr,

arch: &’staticstr,

family: &’staticstr,

/// 编译期常量:为鸿蒙目标编译时才为 true

ohos: bool,

/// 构建期由 tauri-cli 注入的环境变量;手工构建时可能为 None

ohos_library: Option<&’staticstr>,

ohos_package: Option<&’staticstr>,

}

#[tauri::command]

fn app_info() -> AppInfo {

AppInfo {

product: env!(“CARGO_PKG_NAME”),

version: env!(“CARGO_PKG_VERSION”),

os: std::env::consts::OS,

arch: std::env::consts::ARCH,

family: std::env::consts::FAMILY,

ohos: cfg!(target_env = “ohos”),

ohos_library: option_env!(“WRY_OHOS_LIBRARY”),

ohos_package: option_env!(“WRY_OHOS_PACKAGE”),

}

}

这个命令刻意做成了“自证式”的:ohos 字段用编译期 cfg 判断,ohos_library / ohos_package 直接把构建期注入的环境变量读回来。窗口里看到什么值,就说明构建环境是什么样,不用猜。

5.3 从 demo 到自己的工程:改动清单

文件 改动 为什么
src-tauri/Cargo.toml [package] name = “harmony-tauri-app”

([lib] name 保持 tauri_demo_lib)

包名是自己的;库名先不动,它与 ArkTS 侧的 moduleName、import 语句、类型声明目录三处绑定,改一处不够
src-tauri/Cargo.toml tauri 依赖加 features = [“custom-protocol”] 第 7 章的核心坑
src-tauri/tauri.conf.json productName

/ identifier(com.atomcode.harmonytauri)/ withGlobalTauri / 窗口 label+url / frontendDist: “../web”

应用身份与前端来源
src-tauri/src/lib.rs 新增 app_info 命令并注册;保留 demo 的 N-API 回调样例 演示 IPC 与自证环境
gen/ohos/AppScope/app.json5 bundleName = “com.atomcode.harmonytauri” 必须与 tauri 的 identifier 一致
gen/ohos/AppScope/resources/base/element/string.json app_name

改成中文应用名

桌面显示名
gen/ohos/entry/src/main/resources/base/element/string.json 模块描述 / Ability 标签 同上
gen/ohos/assets/tauri.conf.json 用本工程配置覆盖 运行时读的是这一份
web/

(新增)

index.html

+ main.js + style.css

零依赖前端
删除 demo 的 src/、index.html、vite.config.ts、package.json 等 去掉 Vite/Vue 依赖 不再需要打包器

库名为什么先不改:它是整套里最容易牵一发动全身的地方。先把链路跑通,再改名是几分钟的事(改 Cargo.toml 的 [lib] name、EntryAbility.ets 的 moduleName 与 import、oh-package.json5 的依赖项、types/lib<库名>/ 目录名,然后重新构建)。

6. Rust 侧:依赖、编译与链接

6.1 依赖从哪来:一个 fork 加两处 patch

鸿蒙支持目前不在 tauri 的正式发布版里,必须指向 git:

[dependencies]

tauri = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony”, features = [

“custom-protocol”,

] }

tauri-plugin-opener = “2”

napi-ohos = “1.1”

napi-derive-ohos = “1.1”

# 把 openharmony-ability 固定到 2026-06-30 的 295a276a

# (上游 c6c4c9a 移除了 webview feature 之前的最后一个可用状态)

[patch.crates-io]

openharmony-ability = { git = “https://github.com/harmony-contrib/openharmony-ability.git”, rev = “295a276a699ba2352addc69b8cacb6acd3be6ab1” }

openharmony-ability-derive = { git = “https://github.com/harmony-contrib/openharmony-ability.git”, rev = “295a276a699ba2352addc69b8cacb6acd3be6ab1” }

# 把整条 tauri 家族都钉到同一个分支上,避免 crates.io 上的旧版本混进来

tauri = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony” }

tauri-runtime = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony” }

tauri-utils = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony” }

tauri-macros = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony” }

tauri-runtime-wry = { git = “https://github.com/richerfu/tauri”, branch = “feat/open-harmony” }

两点说明:

  • 用户指定的 tauri-apps/tauri 的同名分支与这里的 fork 内容一致(fork 的 manifest 里已经声明了 wry/tao 的同源 rev)。参考 demo 把依赖指向 fork,是因为它同时把 wry、tao 也钉在了带 ohos 后端的提交上,我沿用了这套经过验证的组合。
  • 上面那段 [patch.crates-io] 里的 tauri* 条目不是笔误:patch 会把传递依赖里从 crates.io 拉到的 tauri 家族 crate 也换成 git 版本,避免版本分裂。构建日志里能看到 tauri-utils v2.7.0 与 v2.8.3 两个版本同时存在,就是这个 patch 生效的痕迹。

实际拉到的关键版本(来自构建日志):

crate 版本 / 来源
tauri 2.8.5(fork feat/open-harmony @ c818cc1d)
tauri-runtime-wry 2.8.1(同源)
wry 0.55.1(richerfu/wry @ ae8b21b5)
tao 0.35.3(richerfu/taofeat-ohos-webview @ 9d51be24)
openharmony-ability 0.3.0(rev 295a276a)
napi-ohos 1.1.6
ohos-arkui-sys

/ ohos-web-sys / ohos-xcomponent-sys 等

0.0.1 ~ 0.2.0(ohos 系统能力绑定)

6.2 坑一、坑二:cargo 在这台机器上取依赖

故障 A:cargo 内置 git 客户端 TLS 失败

error: failed to load source for dependency `openharmony-ability`

Caused by: unable to update https://github.com/harmony-contrib/openharmony-ability.git?rev=295a276a#295a276a

Caused by: failed to clone into: /storage/Users/currentUser/.cargo/git/db/openharmony-ability-7e6401132ac36172

Caused by: revision 295a276a699ba2352addc69b8cacb6acd3be6ab1 not found

Caused by: SSL error: unknown error; class=Ssl (16)

if a proxy or similar is necessary `net.git-fetch-with-cli` may help here

cargo 自带的 HTTP/git 传输栈在这台机器上握手失败(class=Ssl)。而系统 git 明明是好用的——同一个仓库 git ls-remote 2.29 秒就返回了。所以按 cargo 自己的提示,把 git 操作交给系统 git:

# ~/.cargo/config.toml

[net]

git-fetch-with-cli = true

故障 B:cargo 的 git checkout 不重试 EINTR

换用系统 git 之后,下载环节通了,但检出(checkout)环节开始翻车——checkout 是 cargo 自己用 Rust 实现的,而它把 EINTR 当致命错误:

Caused by: failed to stat ‘/storage/…/.cargo/git/checkouts/tauri-96ba84dc1b8a2872/c818cc1/crates/tauri-bundler/src/bundle/linux/util.rs’:

Interrupted system call; class=Os (2); code=Exists (-4)

之后每次重试都死在另一个文件上(tauri-schema-generator/…、tauri-cli/templates/…),因为 tauri 仓库文件太多,一整趟 checkout 里不撞上 EINTR 的概率很低。重试不解决问题。

思路转一下:下载和检出可以用别的工具做。git 是 C 程序,会自行重试 EINTR;tar 也一样。于是:

  1. 让 cargo 把仓库下载到它自己的 git db(这一步用系统 git,已经通了);
  2. cargo 检出失败时,从错误信息里取出出问题的 checkout 目录路径;
  3. 用 git archive + tar 手工把该目录铺好;
  4. 补上 cargo 认可的标记文件 .cargo-ok(空文件,只要存在且内容匹配就算检出有效),然后重试。

核心逻辑(完整脚本见第 11 章):

fix_checkout() {

root=”$1″                                  # …/git/checkouts/tauri-96ba84dc1b8a2872/c818cc1

repo=$(basename “$(dirname “$root”)”)      # tauri-96ba84dc1b8a2872

rev=$(basename “$root”)                    # c818cc1

db=”$HOME/.cargo/git/db/$repo”

rm -rf “$root”; mkdir -p “$root”

git –git-dir=”$db” archive –format=tar “$rev” | tar -xf – -C “$root”

: >”$root/.cargo-ok”                       # cargo 用它判断 checkout 可用

}

实测效果:四个 git 依赖(openharmony-ability、tauri、tao、wry)逐个被铺好,之后 cargo 认账,继续去下载 crates.io 上的包:

/storage/Users/currentUser/.cargo/git/checkouts/openharmony-ability-…/295a276   文件数=159   ok=yes

/storage/Users/currentUser/.cargo/git/checkouts/tauri-96ba84dc1b8a2872/c818cc1   文件数=1109  ok=yes

/storage/Users/currentUser/.cargo/git/checkouts/tao-3928fb55dcb557e2/9d51be2     文件数=174   ok=yes

/storage/Users/currentUser/.cargo/git/checkouts/wry-9b73cbcb28a49192/ae8b21b     文件数=132   ok=yes

6.3 构建期必须注入的环境变量

绕过 tauri-cli 之后,它原本替我们做的注入要自己补。这些变量的取值口径在 crates/tauri-cli/src/mobile/open_harmony/mod.rs 的 get_config() 里写得很清楚:

变量 取值 谁在用 漏了会怎样
WRY_OHOS_PACKAGE 包名(com.atomcode.harmonytauri) wry/tauri 的构建脚本 生成的原生侧包名信息不对
TAURI_OHOS_PACKAGE_UNESCAPED 同上,未转义 同上 同上
WRY_OHOS_LIBRARY Cargo.toml

的 [lib] name

同上 库名对不上,加载失败
TAURI_OHOS_PROJECT_PATH gen/ohos

的绝对路径

同上 找不到鸿蒙工程,构建脚本报错
OHOS_NDK_HOME 含 native/ 的 SDK 目录 ohos-arkui-sys

等 ohos-* crate 的 build.rs

panicked at ohos-arkui-sys-0.0.1/build.rs:4:42: OHOS_NDK_HOME not set: NotPresent
OHOS_HOME 同上 部分 ohos 工具链 同上(不同 crate 认的名字不一样)
TAURI_ENV_DEBUG=false 固定值 tauri

crate 的 build.rs

被判成 dev 构建,前端资源加载失败(第 7 章)

OHOS_NDK_HOME 那一条是我第一次构建时踩到的真实报错,原文:

error: failed to run custom build command for `ohos-arkui-sys v0.0.1`

thread ‘main’ panicked at …/ohos-arkui-sys-0.0.1/build.rs:4:42:

OHOS_NDK_HOME not set: NotPresent

本机上这些路径都能从环境推导,不需要写死:

export HMOS_CLT_HOME=/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0

export OHOS_HOME=”$HMOS_CLT_HOME/sdk/default/openharmony”

export OHOS_NDK_HOME=”$OHOS_HOME”

6.4 坑三:没有cc,而且链接出来的东西还不能执行

环境变量补齐后,构建会走到一个很直接的错误:

error: linker `cc` not found

|

= note: No such file or directory (os error 2)

rustc 生成可执行文件/动态库时要调系统 C 链接器(默认名 cc),而本机没有。可用的替代品是 OpenHarmony SDK 里那套能在设备上原生运行的 LLVM:

SDK=/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/sdk/default/openharmony

ls “$SDK/native/llvm/bin” | head

# aarch64-unknown-linux-ohos-clang    ← 171 字节的包装脚本

# aarch64-unknown-linux-ohos-clang++

# clang  clang++  ld.lld  llvm-ar  llvm-objcopy  llvm-readelf …

那个 aarch64-unknown-linux-ohos-clang 的内容值得一看,它替我们补了三件事:

#!/bin/sh

SOURCE=$(dirname — “$( readlink -f — “$0″; )”)

exec $SOURCE/clang \

-target aarch64-linux-ohos \

–sysroot=$SOURCE/../../sysroot \

-D__MUSL__ \

“$@”

但是——链路还没通。用它链接出的产物执行时会:

$ ./libtest_bin

sh: ./libtest_bin: Operation not permitted      # 126

因为未签名。第 2.3 节那张三态表就是在这个阶段被测出来的。我试过两条弯路,记下来省得你重复:

尝试 结果
链接时加 -Wl,–code-sign(lld 确有这个开关) .codesign

段变大(20 KB → 24 KB),但内核依然不认,Operation not permitted

直接 binary-sign-tool sign -inFile x -outFile x -selfSign 1 报 [WriteCodeSignBlock] .codesign section already exists,工具拒绝在已有占位段上签

能用的组合是先剥段、再自签名:

SDK=/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/sdk/default/openharmony

OC=”$SDK/native/llvm/bin/llvm-objcopy”

BST=”$SDK/toolchains/lib/binary-sign-tool”

# 1) 摘掉 lld 留下的占位 .codesign 段

“$OC” –remove-section=.codesign ./out ./out.un && mv -f ./out.un ./out

# 2) 自签名(-selfSign 1 用内置自签证书,不需要额外密钥)

“$BST” sign -inFile ./out -outFile ./out -selfSign 1

# 3) 这次能执行了

./out

手工做是不可持续的:**cargo 每次重新链接都会生成新文件,包括过程宏的 .so**。正确做法是让链接器自己带签名——把上面三步封成一个“链接器包装器”:

#!/bin/sh

# ~/.harmonybrew/libexec/ohos-cc-sign

# 1. 系统里没有 cc/ld,链接交给 OpenHarmony SDK 自带的 clang;

# 2. 链接产物必须自签名,否则内核以 “Operation not permitted” 拒绝执行;

#    而 lld 会预留一个占位 .codesign 段,binary-sign-tool 见到它会报

#    “.codesign section already exists”,所以先摘掉再签。

set -u

SDK=”${OHOS_SDK_HOME:-/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/sdk/default/openharmony}”

# 按调用名挑选 C / C++ 驱动:ohos-cc-sign -> clang,ohos-cxx-sign -> clang++

# (C++ 必须用 clang++,否则链接期不会带上 C++ 标准库)

case”${0##*/}”in

*cxx* | *++*) CLANG=”$SDK/native/llvm/bin/aarch64-unknown-linux-ohos-clang++” ;;

*) CLANG=”$SDK/native/llvm/bin/aarch64-unknown-linux-ohos-clang” ;;

esac

OBJCOPY=”$SDK/native/llvm/bin/llvm-objcopy”

SIGNTOOL=”$SDK/toolchains/lib/binary-sign-tool”

# 从参数里取出输出文件(cargo/rustc 与 cc-rs 都用 -o)

out=””

prev=””

for a in”$@”; do

case”$prev”in

-o | –output) out=”$a” ;;

esac

case”$a”in

-o?*) out=”${a#-o}” ;;

–output=*) out=”${a#–output=}” ;;

esac

prev=”$a”

done

“$CLANG””$@” || exit $?

[ -n “$out” ] && [ -f “$out” ] || exit 0

tmp=”$out.unsigned.$$”

if”$OBJCOPY” –remove-section=.codesign “$out””$tmp” 2>/dev/null; then

[ -x “$tmp” ] || chmod 755 “$tmp”

mv -f “$tmp””$out” || rm -f “$tmp”

fi

“$SIGNTOOL” sign -inFile “$out” -outFile “$out” -selfSign 1 >/dev/null 2>&1

exit 0

几个设计点:

代码 为什么
case “${0##*/}”

按调用名切 clang/clang++

一个脚本兼作 C 与 C++ 驱动;C++ 用 clang 当驱动会漏掉标准库链接
解析 -o / –output 输出路径只能从参数里取,rustc 与 cc-rs 都用 -o
先 –remove-section=.codesign 再 sign 唯一能过 binary-sign-tool 的顺序
签名失败不返回非零 只链 .o/.rlib 时本就不需要签名,不该让构建失败
OHOS_SDK_HOME

可覆盖

hmos-clt 升级后 SDK 路径里的版本号会变

装好并建一个 C++ 名字的入口:

chmod 755 ~/.harmonybrew/libexec/ohos-cc-sign

ln -sfn ohos-cc-sign ~/.harmonybrew/libexec/ohos-cxx-sign   # 名字带 cxx,脚本会自动切 clang++

然后把它告诉 cargo(~/.cargo/config.toml):

[net]

# 本机 cargo 内置的 git 客户端取 GitHub 时 TLS 会失败(SSL error, class=Ssl),

# 交给系统 git 去取。

git-fetch-with-cli = true

[target.aarch64-unknown-linux-ohos]

linker = “/home/<你的用户名>/.harmonybrew/libexec/ohos-cc-sign”

ar = “/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/sdk/default/openharmony/native/llvm/bin/llvm-ar”

[env]

# 给需要编译 C 代码的 crate(*-sys 那类,走 cc-rs)用

CC_aarch64_unknown_linux_ohos = “/home/<你的用户名>/.harmonybrew/libexec/ohos-cc-sign”

AR_aarch64_unknown_linux_ohos = “/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/sdk/default/openharmony/native/llvm/bin/llvm-ar”

6.5 构建脚本与实测耗时

把 6.3、6.4 节的东西合起来就是一个可重复的构建脚本(tools/build-rust.sh):

#!/bin/sh

set -eu

APP_DIR=$(cd”$(dirname “$0″)/..” && pwd)

RUST_DIR=”$APP_DIR/src-tauri”

OHOS_PROJECT=”$RUST_DIR/gen/ohos”

LIB_NAME=tauri_demo_lib          # 与 src-tauri/Cargo.toml 的 [lib] name 一致

BUNDLE=com.atomcode.harmonytauri # 与 gen/ohos/AppScope/app.json5 的 bundleName 一致

TARGET=aarch64-unknown-linux-ohos

export HMOS_CLT_HOME=”${HMOS_CLT_HOME:-/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0}”

export OHOS_HOME=”${OHOS_HOME:-$HMOS_CLT_HOME/sdk/default/openharmony}”

export OHOS_NDK_HOME=”${OHOS_NDK_HOME:-$OHOS_HOME}”

export PATH=”$HOME/.harmonybrew/bin:$HMOS_CLT_HOME/ohpm/bin:$HMOS_CLT_HOME/hvigor/bin:$PATH”

# tauri-cli 构建时会注入这些变量,手工构建必须自己给(见 6.3 节)

export WRY_OHOS_PACKAGE=”$BUNDLE”

export TAURI_OHOS_PACKAGE_UNESCAPED=”$BUNDLE”

export WRY_OHOS_LIBRARY=”$LIB_NAME”

export TAURI_OHOS_PROJECT_PATH=”$OHOS_PROJECT”

export TAURI_ENV_DEBUG=false

cd”$RUST_DIR”

# 同步运行时配置:OHOS 侧运行时读的是 HAP 里的 assets/tauri.conf.json

echo”== 同步 assets/tauri.conf.json ==”

mkdir -p “$OHOS_PROJECT/assets”

cp -f “$RUST_DIR/tauri.conf.json””$OHOS_PROJECT/assets/tauri.conf.json”

echo”== 编译 $LIB_NAME($TARGET,release)==”

cargo build –release –target “$TARGET” –lib

SO=”$RUST_DIR/target/$TARGET/release/lib$LIB_NAME.so”

DEST=”$OHOS_PROJECT/entry/libs/arm64-v8a”

mkdir -p “$DEST”

cp -f “$SO””$DEST/”

ls -l “$DEST/lib$LIB_NAME.so”

实测耗时(20 核 aarch64,release):

场景 耗时
首次全量(含 300+ crate) 2 分 02 秒
改动 tauri.conf.json 后重编(配置嵌入二进制,需重新编译 tauri-codegen 与自身 crate) 44 秒
改动特性(custom-protocol)后重编 30 秒
只改 lib.rs 通常 10 ~ 20 秒

一个会在日志里出现、但完全无害的警告:

warning: hard linking files in the incremental compilation cache failed. copying files instead.

本机文件系统不支持硬链接,cargo 自动退化为复制文件,只影响增量编译的缓存效率,不影响正确性。

7. 三个坑:前端资源为什么加载不出来

这一章是整篇文章里最值得看的部分。它会经历一次完整的“界面能打开、但里面是空的”的排查,并且三个不同的根因指向同一句报错。

7.1 现象与第一课:这条错误不在日志里

应用装上了,进程也在跑:

$ hdc shell “ps -ef | grep -i harmonytauri | grep -v grep”

20020185  58455  670  0 16:46:31 ?  00:00:05 com.atomcode.harmonytauri

20100390  58695 5352  0 16:46:31 ?  00:00:00 com.atomcode.harmonytauri:gpu

20100391  58823 5352  0 16:46:31 ?  00:00:00 com.atomcode.harmonytauri:render

20100392  58849 5352  0 16:46:31 ?  00:00:00 com.atomcode.harmonytauri:render

窗口标题也是对的,但 webview 里只有一行字:

Failed to request tauri://localhost/: builder error for url (tauri://localhost/)

第一课:这句话是渲染在 webview 错误页里的文本,不会进 hilog。 我一开始在 hilog 里 grep 这个关键字,得到 0 次命中,就误判“已经修好了”——直到用界面节点树去读才打脸。

正确的验证手段是 devecocli 的 ui 子命令,它能直接读到设备上的界面节点(包括 webview 内部渲染出来的文本):

# 读界面节点树

devecocli ui layout

# 截图(我会把 PNG 拉下来直接看)

devecocli ui screenshot –path ./build-output/screenshot.png

出问题时的节点树长这样(节选):

DecorBar#ContainerModalTitleRow [597,362,2687,432]

Text#enhanceAppLabel [694,379,899,415] “鸿蒙 Tauri 应用”

Stack#ContainerModalStack [597,432,2687,1756]

genericContainer [612,456,2674,490] “Failed to request tauri://localhost/: builder error for url (tauri://localhost/)”

修好之后的同一位置(节选):

Stack#ContainerModalStack [597,433,2687,1558]

rootWebArea [597,433,2687,1558] scrollable

heading [852,493,2434,561] “鸿蒙 Tauri 应用”

button [891,837,1120,907] “获取应用信息” clickable

genericContainer [891,932,2395,1369] “{\n  \”product\”: \”harmony-tauri-app\”,\n  \”ohos\”: true, …”

paragraph [891,1703,1999,1734] “Hello, 鸿蒙开发者! You’ve been greeted from Rust!”

注意最后一行:连 greet 命令的返回都出现在节点文本里了——这同时也证明了 IPC 是通的。这种“读 UI 树”的验证方式在移动端调试里非常高效,不用靠肉眼比对截图。

7.2 根因一:这次构建被判成了 dev 构建

报错文案很好用,直接拿去搜源码,命中在 tauri 的资源协议处理器里:

// crates/tauri/src/protocol/tauri.rs:190

Err(e) => {

let error_message = format!(

“Failed to request {}: {}{}”,

url.as_str(),

e,

…

而这段代码位于 #[cfg(all(dev, mobile))] 的分支里——也就是说,只有“dev 构建 + 移动端”才会走进这条把请求转发给开发服务器的路径:

// crates/tauri/src/manager/webview.rs:40

pub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile));

那 dev 是谁给的呢?顺着一条链往上追:

// crates/tauri-build/src/lib.rs:509

cfg_alias(“dev”, is_dev());

// crates/tauri-build/src/lib.rs:417

pub fn is_dev() -> bool {

env::var(“DEP_TAURI_DEV”).expect(“missing `cargo:dev` instruction, please update tauri to latest”)

== “true”

}

DEP_TAURI_DEV 来自 tauri crate 自己的构建脚本:

// crates/tauri/build.rs:252-261

alias(“custom_protocol”, custom_protocol);

alias(“dev”, dev);

println!(“cargo:dev={dev}”);

let target_os = std::env::var(“CARGO_CFG_TARGET_OS”).unwrap();

let target_env = std::env::var(“CARGO_CFG_TARGET_ENV”).unwrap_or_default();

let mobile = target_os == “ios” || target_os == “android” || target_env == “ohos”;

alias(“desktop”, !mobile);

alias(“mobile”, mobile);

结论很清楚:

  • mobile 对鸿蒙是成立的(target_env == “ohos”);
  • dev 的判定则是 Tauri 的老规矩——没有启用 custom-protocol 特性就视为 dev;
  • 于是 PROXY_DEV_SERVER = true,资源请求被转发给”开发服务器”;而我们的配置里没有 devUrl,转发目标退化成 tauri://localhost/,reqwest 解析不了这个 scheme,抛出 builder error。

修法(两处一起做):

# src-tauri/Cargo.toml

tauri = { git = “…”, branch = “feat/open-harmony”, features = [“custom-protocol”] }

# 构建脚本里显式声明生产构建

export TAURI_ENV_DEBUG=false

修复前后的体积变化是个很好的旁证:.so 从 10.7 MB 降到 9.4 MB,掉的那 1.3 MB 正是 dev 专属代码。

这里也有个值得记住的结论:**用 cargo build –release 并不等于”生产构建”**。Tauri 的 dev/prod 判定走的是自己的 cfg 体系,与 cargo 的 profile 是两回事。

7.3 根因二:运行时读的那份配置没同步

Tauri 的 gen/ohos/assets/tauri.conf.json 是运行时真正读的配置(tauri-cli 的 inject_resources 会在每次构建时把解析后的配置写进去)。我们改成手工构建之后,这份文件还是参考 demo 的旧内容:

{

“productName”: “tauri-demo”,

“identifier”: “com.ranger.tauri-demo”,

“build”: { “devUrl”: “http://localhost:1420/”, “frontendDist”: “../dist” },

“app”: { “withGlobalTauri”: false }

}

它和我们编译进二进制的配置完全对不上。修法就是把它纳入构建流程——在 tools/build-rust.sh 里加一步同步(见 6.5 节脚本)。这也解释了为什么”改了 tauri.conf.json 却没有任何效果”是很常见的一种困惑。

7.4 根因三:窗口没有显式指定 url

再看 demo 那份“已解析”的配置,它的窗口是:

“windows”: [{ “label”: “main”, “url”: “index.html”, “title”: “tauri-demo”, … }]

而我们的窗口只写了标题和尺寸。缺了 url,窗口就会去请求根路径——**正好就是报错里的 tauri://localhost/**:

“windows”: [

{ “label”: “main”, “url”: “index.html”, “title”: “鸿蒙 Tauri 应用”, “width”: 900, “height”: 640 }

]

7.5 三条经验

  1. 同一句报错可能对应多个根因,改完一个别急着收工,用 ui layout 再读一次界面;
  2. 验证要用”能读到内容”的手段(界面节点树 / 截图),不要只看日志——错误页不进日志;
  3. 移动端 Tauri 有三份配置概念:源码里的 tauri.conf.json(编译期嵌入)、gen/ohos/assets/tauri.conf.json(运行期读取)、以及窗口/安全等默认值补全后的”已解析配置”。三者不一致时,症状往往就是”界面空白 + 一句含糊的错误”。

7.6 顺带一个不致命的警告

hvigor 构建时会有一条警告:

WARN: …/oh_modules/.ohpm/@ohos-rs+ability@0.4.0-beta.5/oh_modules/@ohos-rs/ability/src/main/module.json does not exist.

This library will not be merged. This may occur when the project is manually modified and the local library

is not declared in build-profile.json5.

它说的是 @ohos-rs/ability 这个 ohpm 包里没有 module.json,hvigor 不知道该怎么把它的资源合并进主模块。实测不影响运行(应用正常启动、webview 正常渲染),属于上游包元数据不全导致的提示。知道它的存在即可,不必为此改工程结构。

8. HAP 构建与签名

8.1 先把 HAP 组出来

Rust 侧的 .so 放进 gen/ohos/entry/libs/arm64-v8a/ 之后,就可以用 hvigor 组装 HAP 了:

cd src-tauri/gen/ohos

export PATH=”$HMOS_CLT_HOME/ohpm/bin:$HMOS_CLT_HOME/hvigor/bin:$PATH”

ohpm install

hvigorw assembleHap –no-daemon –stacktrace

ohpm install 会从鸿蒙仓库拉 ArkTS 依赖:

ohpm INFO: fetch package done 1 @ohos-rs/ability from https://repo.harmonyos.com/ohpm/@ohos-rs/ability/-/ability-0.4.0-beta.5.har

install completed in 1s 16ms

hvigor 的任务链走完这一串(节选)后就会产出产物:

Finished :entry:default@ConfigureCmake

Finished :entry:default@BuildNativeWithCmake        # 编译模板自带的 C++ 库

Finished :entry:default@BuildNativeWithNinja

Finished :entry:default@ProcessLibs                 # 把 libs/arm64-v8a 下的 .so 收进包

Finished :entry:default@CompileArkTS

Finished :entry:default@PackageHap

Finished :entry:default@SignHap

Finished :entry:assembleHap

BUILD SUCCESSFUL in 12 s 217 ms

产物在:

entry/build/default/outputs/default/entry-default-unsigned.hap

构建期会有两条警告,都不致命:

WARN: The project has not explicitly set the ‘targetSdkVersion’ version at build-profile.json5. It is recommended to configure it.

WARN: …/@ohos-rs+ability@0.4.0-beta.5/…/module.json does not exist. This library will not be merged.

以及一条无害的 clang 提示(hvigor 给 C++ 传了它不用的参数):

clang++: warning: argument unused during compilation: ‘–gcc-toolchain=…/hms/native/BiSheng’ [-Wunused-command-line-argument]

8.2 坎一:hvigor 的 signingConfigs 不接受明文密码

我最初的思路很自然:SDK 里就带着 OpenHarmony 的签名材料(OpenHarmony.p12、OpenHarmonyApplication.pem、profile 模板),那把密码直接写进 build-profile.json5 不就行了?结果构建到 SignHap 直接失败:

hvigor ERROR: Failed :entry:default@SignHap

hvigor ERROR: 00303116 Configuration Error

Error Message: The length of the storePassword or keyPassword field in the signature configuration is less than 32.

At file: …/gen/ohos/build-profile.json5

at DecipherUtil.decryptPwd (…/hvigor-ohos-plugin/src/utils/decipher-util.js)

原因是 hvigor 会把这两个字段当密文解密(DevEco Studio 用本地密钥加密后写入,密文长度 ≥ 32),明文会被直接判为非法。这条路只有两个出口:

  1. 让 hvigor 产出未签名包,签名用 hap-sign-tool 自己来(CI 常用做法);
  2. **让 DevEco / devecocli 把加密后的签名配置写进 build-profile.json5**(8.4 节)。

我先把第 1 条走通了:

# build-profile.json5 里 “signingConfigs”: [],去掉产品上的 “signingConfig”

hvigorw assembleHap –no-daemon      # 产出 entry-default-unsigned.hap

hap-sign-tool sign-app \

-keyAlias “openharmony application release” -signAlg “SHA256withECDSA” -mode “localSign” \

-appCertFile “$SDK_LIB/OpenHarmonyApplication.pem” \

-profileFile “./signing/harmony-tauri-app.p7b” \

-inFile  “entry-default-unsigned.hap” \

-outFile “harmony-tauri-app-signed.hap” \

-keystoreFile “$SDK_LIB/OpenHarmony.p12” -keyPwd “123456” -keystorePwd “123456”

其中那个 .p7b 是我自己签出来的:把 SDK 的 UnsgnedDebugProfileTemplate.json 里的包名、设备 UDID、有效期改成本机信息,再用 hap-sign-tool sign-profile 签一遍。本地验签是通过的:

$ hap-sign-tool verify-app -inFile harmony-tauri-app-signed.hap -outCertChain /tmp/v.cer -outProfile /tmp/v.p7b

INFO  – hap verify successed!

INFO  – verify-app success

8.3 坎二:9568257 —— 签名合法,但设备不认

本地验签过了,装到设备上却是:

$ hdc install -r build-output/harmony-tauri-app-signed.hap

[Info]App install path:…/harmony-tauri-app-signed.hap msg:error: failed to install bundle.

code:9568257 error: fail to verify pkcs7 file.

这个错误码的官方解释很直白(华为开发者 FAQ / 社区答复):

NEXT 项目不允许 OH 根证书的应用安装,请使用商用证书签名安装。

出现该问题的原因是应用当前使用的签名不符合 HarmonyOS 应用签名要求。通常是由于当前使用的是 OpenHarmony 应用的签名,需替换为 HarmonyOS 应用的签名。

也就是说:SDK 自带的 OpenHarmony.p12 / OpenHarmonyApplication.pem 这一套 OpenHarmony 根证书,在 HarmonyOS(NEXT)设备上天生不被信任,只能用于本地验证与 OpenHarmony 设备/模拟器。要装到鸿蒙 PC 上,必须换成华为侧签发的材料:

  • 在 AppGallery Connect(AGC) 申请调试证书(.cer)和密钥库(.p12);
  • 在 AGC 设备管理里用它注册本机 UDID;
  • 在 AGC 申请 HAP Provision Profile(选中该设备)得到 .p7b。

本机 UDID 的获取方式:

$ hdc shell bm get –udid

udid of current device is :

2B7F77180E0C23FA383DC8F24BD9CD0D3AF444A700351F27E46D6E06D6B3DF89

这是一条外部依赖:它需要华为开发者账号。所以我没有手工去 AGC 走一遍,而是用了 DevEco 官方 CLI —— devecocli 把这一整套自动化了。

8.4 用 devecocli 拿到 AGC 签名材料

devecocli 是 DevEco 的命令行工具(npm 包 @deveco/deveco-cli),它把 hvigor / ohpm / hdc / 签名流程都包了一层:

npm install -g @deveco/deveco-cli

$ devecocli auth status

Current user: 坚****          # 已登录华为开发者账号

$ cd src-tauri/gen/ohos && devecocli signature generate –product default

Executing signature generate command

Start generating p12

Start generating csr

Start generating certificate

Start generating profile

Signing config written to …/src-tauri/gen/ohos/build-profile.json5

Signature generation completed successfully.

它在本地产出 .p12 / .csr / .cer / .p7b 四件套(放在 ~/.ohos/config/),并把 signingConfigs 写进工程配置——注意密码是加密态,这正是 hvigor 需要的形态:

“signingConfigs”: [

{

“name”: “default”,

“type”: “HarmonyOS”,

“material”: {

“certpath”: “/storage/Users/currentUser/.ohos/config/default_ohos_….cer”,

“keyAlias”: “debugKey”,

“keyPassword”: “0000001b75313f897d24c8499e1abdde2e0f9982a81ca227f3d5bb29fad8bd4a9c4c0e6906f26f2fd25226”,

“profile”: “/storage/Users/currentUser/.ohos/config/default_ohos_….p7b”,

“signAlg”: “SHA256withECDSA”,

“storeFile”: “/storage/Users/currentUser/.ohos/config/default_ohos_….p12”,

“storePassword”: “0000001b75313f897d24c8499e1abdde2e0f9982a81ca227f3d5bb29fad8bd4a9c4c0e6906f26f2fd25226”

}

}

]

两个前提条件(脚本化时要注意):已登录(devecocli auth login,会拉起浏览器 OAuth)且设备已连接(要把 UDID 写进 profile)。

8.5 一条命令:构建 + 签名 + 安装 + 启动

签名配置就位之后,整个部署就是一条命令:

cd src-tauri/gen/ohos

devecocli run –module entry

真实输出(尾部):

[hvigor build] Running…

> hvigor Finished :entry:default@CompileArkTS… after 5 s 209 ms

> hvigor Finished :entry:default@PackageHap… after 170 ms

> hvigor Finished :entry:default@SignHap… after 532 ms

> hvigor BUILD SUCCESSFUL in 11 s 951 ms

Build completed successfully.

Installing artifacts to device 127.0.0.1:34953…

App installed successfully

Launching com.atomcode.harmonytauri/EntryAbility…

Application ‘com.atomcode.harmonytauri’: start ability successfully.

它内部做的事和 8.1 ~ 8.4 节手工做的一模一样(ohpm install → hvigor 构建 → SignHap → hdc 安装 → aa start),只是不用自己拼参数。常用参数:

参数 用途
–module entry 指定模块(多模块工程必须给)
–build-mode release 出 release 包(默认 debug)
–uninstall 先卸载旧包(换过签名密钥时很有用)
–skip-build 跳过构建,只部署已有产物
–device <serial> 多设备时指定目标

8.6 签名工具速查(hap-sign-tool)

SDK 里的签名工具是原生二进制(不是 jar),用法是 <子命令> [参数],不吃 –help。常用三个子命令(均在 $DEVECO_SDK_HOME/default/openharmony/toolchains/lib/ 下):

T=$DEVECO_SDK_HOME/default/openharmony/toolchains/lib

# 1) 签 profile(把 Unsgned*ProfileTemplate.json 变成 .p7b)

“$T/hap-sign-tool” sign-profile \

-keyAlias “openharmony application profile release” -signAlg SHA256withECDSA -mode localSign \

-profileCertFile “$T/OpenHarmonyProfileRelease.pem” \

-inFile profile.json -keystoreFile “$T/OpenHarmony.p12” -outFile app.p7b \

-keyPwd 123456 -keystorePwd 123456

# 2) 签 HAP

“$T/hap-sign-tool” sign-app \

-keyAlias “openharmony application release” -signAlg SHA256withECDSA -mode localSign \

-appCertFile “$T/OpenHarmonyApplication.pem” -profileFile app.p7b \

-inFile unsigned.hap -outFile signed.hap \

-keystoreFile “$T/OpenHarmony.p12” -keyPwd 123456 -keystorePwd 123456

# 3) 本地验签(注意:验签通过 ≠ 设备会接受,见 8.3 节)

“$T/hap-sign-tool” verify-app -inFile signed.hap -outCertChain /tmp/v.cer -outProfile /tmp/v.p7b

顺带记一个“顺手发现”:OpenHarmony.p12 里读不出可读的别名(p12 是二进制/加密的),但官方文档给出的小写别名 openharmony application profile release / openharmony application release 在本机实测可用,密码都是 123456。

9. 安装、运行与调试

9.1 安装与启动的两条路径

路径一:devecocli(推荐,一步到位)

cd src-tauri/gen/ohos && devecocli run –module entry

路径二:纯 hdc(CI 或脚本化时用)

# 安装(-r 表示覆盖安装)

hdc install -r build-output/harmony-tauri-app-signed.hap

# 启动指定 Ability

hdc shell aa start -a EntryAbility -b com.atomcode.harmonytauri -m entry

# 卸载(换签名密钥后建议先卸载,否则可能报 “install sign info inconsistent”)

hdc uninstall com.atomcode.harmonytauri

官方文档里还有一种“推送再安装”的等价写法,适合设备端空间受限的场景:

hdc file send entry-default-signed.hap data/local/tmp/app.hap

hdc shell bm install -p data/local/tmp/app.hap

hdc shell rm -rf data/local/tmp/app.hap

9.2 看日志

# 应用侧日志(Rust 的 stdout/stderr 会带 RustStdoutStderr 标签)

hdc shell hilog | grep -iE ‘tauri|RustStdoutStderr’

# 用 devecocli 按包名取日志(含崩溃日志)

devecocli log –bundle-name com.atomcode.harmonytauri

启动一个 ArkWeb 应用时,日志里会出现大量 Chromium 内部行(chromium: 前缀),比如:

I A04510/com.atomcode.harmonytauri/chromium: [background_task_policy.cc:214] visible_page_num_: 1

D A04511/com.atomcode.harmonytauri:gpu/chromium: [vulkan_surface.cc:329] vulkan old swap chain width: 2090 height: 1324

visible_page_num_: 1 表示页面在前台可见——这类行可以用来确认 webview 是否真的起来了(但不是判断“内容渲染正确”的依据,见 9.3)。

9.3 验证界面:读节点树比看截图更有效

devecocli ui 提供了一整套设备 UI 操作:

devecocli ui layout                          # 打印界面节点树(能读到 webview 里的文本)

devecocli ui screenshot –path ./shot.png    # 截图

devecocli ui click 1000 870                  # 点击坐标(可用来点按钮)

devecocli ui text “输入内容” 1500 1640         # 往输入框打字

devecocli ui window list                     # 列出窗口

第 7 章已经演示过:节点树能直接读到 webview 渲染出来的文本。修好之后我读到的内容(节选,注意最后一行是 greet 命令的返回值):

    heading [852,493,2434,561] “鸿蒙 Tauri 应用”

button [891,837,1120,907] “获取应用信息” clickable

genericContainer [891,932,2395,1369] “{\n  \”product\”: \”harmony-tauri-app\”,\n  \”version\”: \”0.1.0\”, … \”ohos\”: true, …}”

textField [891,1605,1999,1677] “鸿蒙开发者” clickable

paragraph [891,1703,1999,1734] “Hello, 鸿蒙开发者! You’ve been greeted from Rust!”

9.4 一个小插曲:别在锁屏状态下验证界面

我第一次跑 ui layout 时拿到的全是锁屏节点:

WindowScene#session18

Stack#SCBScreenLock_Screen_lock_home

Stack#slide_screen_lock

Text#ScreenLock-TimeView_Text_Digital_Text “07:42”

TextInput#screenLock_pc_TextInput_password clickable

Text “使用指纹或密码解锁”

ui layout 只返回屏幕上可见的节点,所以屏幕锁定/应用不在前台时,你看到的就不是应用界面。验证前顺手确认一下:应用是不是前台窗口(必要时 devecocli run 重新拉起一次,或先解锁屏幕)。

9.5 完整的验证清单

检查项 命令 期望
设备在线且是 2in1 devecocli device list device 2in1
Rust 库已进包 unzip -l build-output/*.hap | grep libtauri 看到 libs/arm64-v8a/libtauri_demo_lib.so
包信息正确 unzip -p <hap> module.json bundleName

对、deviceTypes 含 2in1

装上了 hdc shell “bm dump -a | grep harmonytauri” 列出包名
在运行 hdc shell “ps -ef | grep harmonytauri” 有主进程 + gpu/render 子进程
界面渲染正确 devecocli ui layout 能看到自己的界面文本
IPC 通了 点按钮后看 ui layout 节点里出现命令返回值

10. 踩坑总表

按“你在终端里看到了什么”组织,方便直接检索。同一句报错可能对应多个根因(尤其第 3 ~ 5 条)。

报错 / 现象 根因 修法 章节
code:9568257 error: fail to verify pkcs7 file HarmonyOS(NEXT)不信任 OpenHarmony 根证书签的应用 用 AGC 的调试证书 + 绑定本机 UDID 的 Profile;devecocli signature generate 可自动化 8.3 / 8.4
00303116 Configuration Error … storePassword … less than 32 hvigor 要求签名密码是 DevEco 加密态,拒收明文 让 hvigor 出未签名包再 hap-sign-tool sign-app;或让 devecocli 写加密配置 8.2
webview 里只有 Failed to request tauri://localhost/: builder error for url 构建被判成 dev(未启用 custom-protocol),资源请求被转发给开发服务器 tauri 依赖加 features = [“custom-protocol”],并 TAURI_ENV_DEBUG=false 7.2
同上 运行时读的 gen/ohos/assets/tauri.conf.json 没同步 构建脚本里同步注入这份配置 7.3
同上 窗口没写 url,默认请求根路径 窗口加 “url”: “index.html” 7.4
error: linker \

cc` not found`

系统无 C 工具链 用 SDK 的 clang 做链接器包装器 + ~/.cargo/config.toml 6.4
Operation not permitted

(126)

产物的 .codesign 是占位段,签名不合法 剥段 + binary-sign-tool -selfSign 1(包装器已自动做) 2.3 / 6.4
Permission denied

(126)

文件里完全没有.codesign 段(如上游官方构件) 逐个二进制与 .so 都签名 2.3 / 6.4
[WriteCodeSignBlock] .codesign section already exists 直接签名时占位段还在 先 llvm-objcopy –remove-section=.codesign 6.4
panicked at ohos-arkui-sys-0.0.1/build.rs: OHOS_NDK_HOME not set: NotPresent 缺构建期环境变量 注入 OHOS_NDK_HOME(指向含 native/ 的 SDK 目录) 6.3
SSL error: unknown error; class=Ssl (16)

(cargo 取依赖时)

cargo 内置 git 客户端 TLS 在本机不可用 ~/.cargo/config.toml

加 [net] git-fetch-with-cli = true

6.2
cannot access component in path ‘…’: Interrupted system call … cargo 的 git checkout 不重试 EINTR 用 git archive + 空 .cargo-ok 重铺 checkout,再重试 6.2
ohpm

报 node: … Permission denied

PATH 前面有个跑不起来的旧 node 把 ~/.local/bin/{node,npm,npx} 指回可用的 node 4.2
WARN: … @ohos-rs/ability … module.json does not exist. This library will not be merged. ohpm 包元数据不全 忽略,实测不影响运行 7.6
WARN: The project has not explicitly set the ‘targetSdkVersion’ 工程配置未显式声明 忽略(或按提示补上) 8.1
clang++: warning: argument unused … ‘–gcc-toolchain=…’ hvigor 给 C++ 传了不用的参数 忽略 8.1
warning: hard linking files in the incremental compilation cache failed 文件系统不支持硬链接 忽略,只是增量缓存效率略降 6.5
error:install sign info inconsistent 设备上残留了用另一套密钥签的同包名应用 devecocli run –uninstall

/ hdc uninstall <包名>

9.1
zcat: not gzip

+ tar: had errors(解 .tar.xz 时)

本机 toybox 的 tar 不支持 xz 用装好的 xz -dc x.tar.xz > x.tar 再解 上篇教程

11. 工程结构与一键复现

11.1 目录树

harmony-tauri-app/

├── web/                          # 零依赖前端(编译期被嵌入 Rust 库)

│   ├── index.html

│   ├── main.js                   # 通过 window.__TAURI__ 调 Rust 命令

│   └── style.css

├── signing/                      # 自签调试 profile(本地验签用)

│   └── debug-profile.json

├── tools/

│   ├── cargo-fetch-retry.sh      # 拉依赖(自动绕开 EINTR checkout 故障)

│   ├── build-rust.sh             # 编译 Rust 库 + 同步运行时配置 + 放进 gen/ohos

│   ├── build-hap.sh              # hvigorw 出未签名包 + hap-sign-tool 显式签名

│   └── install-hap.sh            # 纯 hdc 安装与启动(失败时打印 UDID 与指引)

├── build-output/                 # 产物与验证截图(screenshot*.png)

└── src-tauri/

├── Cargo.toml                # tauri fork + patch + custom-protocol

├── tauri.conf.json           # 应用身份、窗口、frontendDist

├── src/

│   ├── lib.rs                # 命令(greet/app_info)+ N-API 回调样例

│   └── main.rs               # 桌面入口(鸿蒙不用)

├── tauri-plugin-ohos-demo/   # 参考用的插件工程(Rust + guest-js)

└── gen/ohos/                 # 鸿蒙工程(等同 tauri ohos init 的产物)

├── AppScope/app.json5

├── build-profile.json5   # 内含 devecocli 写入的 signingConfigs

├── assets/tauri.conf.json# 运行时真正读的配置

└── entry/

├── src/main/ets/entryability/EntryAbility.ets

├── src/main/cpp/types/libtauri_demo_lib/Index.d.ts

└── libs/arm64-v8a/libtauri_demo_lib.so   # 构建产物落点

11.2 三个”必须三处一致”的名字

这是配置类问题里最省时间的自查表:

概念 出现的位置(都要一致)
包名 tauri.conf.json

的 identifier;gen/ohos/AppScope/app.json5 的 bundleName;构建期 WRY_OHOS_PACKAGE / TAURI_OHOS_PACKAGE_UNESCAPED

库名 Cargo.toml

的 [lib] name;EntryAbility.ets 的 moduleName 与 import … from ‘lib<名字>.so’;entry/oh-package.json5 里的 “lib<名字>.so”: “file:./src/main/cpp/types/lib<名字>”;以及 types/lib<名字>/ 目录名

应用显示名 gen/ohos/AppScope/resources/base/element/string.json

的 app_name;entry/…/string.json 的 EntryAbility_label

11.3 从零到跑通的完整命令序列

# 0) 前置:Rust(aarch64-unknown-linux-ohos host)、ohpm/hvigorw、devecocli、可用的 node

#    以及 ~/.cargo/config.toml(linker + ar + [net] git-fetch-with-cli)

# 1) 准备工程:从参考 demo 复制 gen/ohos,改包名/应用名/(可选)库名

#    并写好自己的 web/ 前端与 src-tauri/src/lib.rs

# 2) 拉依赖(自动修复 cargo 的 EINTR checkout 故障)

sh tools/cargo-fetch-retry.sh

# 3) 编译 Rust 库并放进 ArkTS 工程

sh tools/build-rust.sh

# 4) 生成 AGC 签名材料(需已登录 + 设备在线)

cd src-tauri/gen/ohos && devecocli signature generate –product default

# 5) 构建 + 签名 + 安装 + 启动

devecocli run –module entry

# 6) 验证界面

devecocli ui layout | head -40

devecocli ui screenshot –path ../../build-output/shot.png

11.4 四个脚本各自解决什么

脚本 解决的问题 关键点
cargo-fetch-retry.sh cargo 取依赖的 TLS + EINTR 双故障 从错误信息里提取出问题的 checkout 目录,用 git archive + 空 .cargo-ok 重铺后重试(最多 N 轮)
build-rust.sh 手工构建缺失的环境注入 注入 4 个 WRY_OHOS_*/TAURI_OHOS_* 变量 + OHOS_NDK_HOME + TAURI_ENV_DEBUG=false;同步 assets/tauri.conf.json;把 .so 复制进 libs/arm64-v8a/
build-hap.sh hvigor 只认加密密码 hvigorw

出未签名包 → hap-sign-tool sign-app 签名;签名材料全部可用 SIGN_* 环境变量替换成 AGC 的材料

install-hap.sh 纯 hdc 部署 hdc install -r

+ aa start;失败时直接打印本机 UDID 与“该去 AGC 做什么”的提示

其中 cargo-fetch-retry.sh 的核心是那段“读错误 → 修 checkout”的循环:

while [ “$i” -lt “$MAX” ]; do

i=$((i + 1))

if cargo fetch >”$LOG” 2>&1; then exit 0; fi

tail -4 “$LOG”

# 从 “cannot access component in path ‘…'” / “failed to stat ‘…'” 里取出 checkout 根目录

root=$(grep -oE “‘/[^’]*git/checkouts/[^’]*'” “$LOG” | head -1 | tr -d “‘” |

sed -E ‘s#(.*/git/checkouts/[^/]+/[^/]+).*#\1#’)

[ -n “$root” ] && [ -d “$root” ] && fix_checkout “$root” || exit 1

done

12. 已知限制与后续方向

12.1 限制

限制 说明与影响
依赖的是 feature 分支 tauri

/wry/tao 的鸿蒙支持都在各仓库的分支上,与上游主线会有漂移;升级时要重新验证,不要盲目 cargo update

需要 openharmony-ability 的固定 rev 上游在 c6c4c9a 之后移除了 webview 相关能力,demo 与本文都钉在 295a276a;跟进上游前先确认这一点是否已恢复
签名与设备绑定 调试 profile 里写着 UDID,换设备要重新申请;release 签名还需要上架流程
库名不好改 [lib] name

牵动 ArkTS 侧四处(见 11.2),建议先把链路跑通再改名

包体积 HAP 约 8.6 MB,其中 Rust .so 9.4 MB(压缩后进包)、还有一份模板带来的 libc++_shared.so(1.2 MB);关掉模板的 C++ 库可以省掉后者
无 rustup 本机 Rust 由 harmonybrew 的包提供,切版本要靠包管理器,而不是 rustup toolchain
首次构建慢 全量 2 分钟起(20 核),改配置触发的重编 30 ~ 44 秒
热重载未实测 devecocli run –hotreload

需要 DevEco ≥ 6.1.1 的 hvigor 能力,本文没有验证

前端依赖生态 零依赖静态前端规避了打包器问题,但反过来说,想上 React/Vue 就要先解决 rollup/esbuild 在本机的可用性

12.2 值得继续做的事

  1. 把库名改成自己的(4 处改动 + 一次重新构建),让工程看起来不像 demo;
  2. 收敛 deviceTypes:当前是 phone/tablet/2in1/tv/car,只做 PC 的话留 2in1 更严谨;
  3. 写一个真正的 Tauri plugin:工程里已带 tauri-plugin-ohos-demo 作为骨架(Rust 侧命令 + 权限 + guest-js),照它改即可;
  4. 走一遍 release 签名:AGC 申请发布证书与发布 Profile,把 –build-mode release 与上架流程补上;
  5. 试试官方 tauri-cli 路线:如果不在意首次编译时间,cargo tauri ohos build/dev 会省掉本文手工注入的那些变量(但要接受它的构建耗时与失败重试成本)。

12.3 一点体会

这套流程里真正“鸿蒙特有”的东西只有三件事:musl + ohos 三元组、必须签名、签名必须是华为侧签发。其余大部分挫折——cargo 的 TLS 与 EINTR、坏 node、打包器不可用、hvigor 只认加密密码——本质上是“在一个很新的、工具链尚不完整的平台上做工程”的通用问题。

所以排查这类问题时,我后来养成的习惯是:先把问题归类(平台差异 / 工具链缺陷 / 框架配置),再决定是“改配置”还是“绕过”。

  • 看到 linker、cc、.codesign、Operation not permitted → 平台差异,改配置能解决;
  • 看到 Interrupted system call、cannot load such file → 工具链缺陷,换工具比调参数有效;
  • 看到 Failed to request tauri://…、界面空白但进程健在 → 框架配置(尤其是”三份配置”不一致),去读框架源码定位比猜快得多。

13. 参考资料

框架与工程

  • Tauri 的鸿蒙分支:https://github.com/tauri-apps/tauri/tree/feat/open-harmony
  • 参考工程(本文的 gen/ohos 与 N-API 回调样例来源):https://github.com/richerfu/tauri-demo
  • Tauri CLI 里的鸿蒙实现(构建与环境变量注入的权威出处):crates/tauri-cli/src/mobile/open_harmony/
  • Tauri CLI 内置的鸿蒙工程模板:crates/tauri-cli/templates/mobile/open-harmony/

Rust 侧

  • Rust 官方平台支持文档(*-unknown-linux-ohos,Tier 2 with Host Tools):https://doc.rust-lang.org/stable/rustc/platform-support/openharmony.html
  • ohos-rs(把 Rust 编译成鸿蒙原生模块 / N-API):https://ohos.rs/docs/basic/quick-start/
  • openharmony-ability(Ability 的 Rust 绑定,本文钉在 295a276a):https://github.com/harmony-contrib/openharmony-ability
  • cargo-ohos(交叉编译 + 自动拉起 SDK 的工具,CI 场景值得一看):https://crates.io/crates/cargo-ohos

鸿蒙侧

  • DevEco CLI(devecocli)文档:https://www.npmjs.com/package/@deveco/deveco-cli
  • 华为开发者文档「搭建流水线」(命令行构建、签名、安装运行的官方流程):https://developer.huawei.com/consumer/cn/doc/doccenter-deveco-studio/ide-command-line-building-app
  • 华为开发者文档「自动签名」(AGC 与本地材料的对应关系):https://developer.huawei.com/consumer/cn/doc/doccenter-deveco-studio/ide-signing-auto

本文基于 2026-10-18 在 HUAWEI MateBook Pro(HarmonyOS 7.0.0.111 / API 26,aarch64,2in1)上的完整实操记录整理。文中所有命令、报错原文、版本号、体积与耗时均来自该机实测;标注为“未实测”的部分请以你机器上的结果为准。

本文由人人都是产品经理作者【nutpi】,微信公众号:【nutpi】,原创/授权 发布于人人都是产品经理,未经许可,禁止转载

题图来自Unsplash,基于 CC0 协议。

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!