跳到内容

一个 Tauri 项目同时兼容 Android 与 HarmonyOS:双 Cargo 清单切换的构建思路

更新于
约 11 分钟阅读
·
1823 字
Tauri
#Rust#Tauri#Android#OpenHarmony#HarmonyOS#Cargo#构建工程
🛈
本文由 AI 辅助生成

本文部分或全部内容由 AI 辅助撰写。AI 生成的内容可能存在不准确之处,请读者注意甄别。

要给一个 Tauri 2 项目同时做 Android 和 HarmonyOS 两套移动端,最大的坑不在 UI,也不在平台 API,而在 Cargo 的依赖解析机制。这篇文章记录我在 agent-mobile 上落地「双 Cargo 清单 + 构建时替换」这套方案的完整思路和实现。

问题在哪

前端(React + TypeScript + Vite)是完全可以复用的,Android 和 HarmonyOS 共用同一份 dist/。真正的差异在 Rust 后端:

  • Android 用 crates.io 上的官方 tauri 2.x,开箱即用。
  • HarmonyOS(OpenHarmony) 官方还没合入主线,支持在 feat/open-harmony 这个 git 分支上。

也就是说,同一份代码,两个平台要用两套来源不同的依赖。而 Cargo 里有个很要命的前提:

[patch.crates-io] 是全局的,且只在 workspace 根清单里生效;写在成员 crate 里的 [patch] 会被 cargo 静默忽略。

同一个 crate 没法「按平台」声明两套依赖,[patch] 也不能放进成员 crate。这就是这套方案要绕过去的两个约束。

方案总览:双清单 + 构建时替换

核心思路很朴素——准备两份清单和两份锁文件,构建哪边就用哪边

src-tauri/
├── Cargo.toml         # Android 版(crates.io 依赖,常驻状态)
├── Cargo.lock         # Android 版锁文件(已提交)
├── Cargo.ohos.toml    # HarmonyOS 版([patch.crates-io] 指向 open-harmony 分支)
├── Cargo.ohos.lock    # HarmonyOS 版锁文件(已提交)
└── ...
  • 平时仓库里躺着的是 Android 版 Cargo.toml / Cargo.lock
  • 构建 HarmonyOS 时,脚本把 Cargo.ohos.toml / Cargo.ohos.lock 临时顶替Cargo.toml / Cargo.lock
  • 执行完构建命令,再原样恢复

HarmonyOS 版清单里就是那段重定向(示意,实际分支/rev 以 Cargo.ohos.lock 里的 pin 为准):

[patch.crates-io]
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "feat/open-harmony" }
tao   = { git = "https://github.com/tauri-apps/tao",   branch = "feat/open-harmony" }
wry   = { git = "https://github.com/tauri-apps/wry",   branch = "feat/open-harmony" }

关键点一:让 src-tauri 独立成 workspace

这是最容易踩的坑。[patch] 只在 workspace 根清单生效,所以必须让「承载 patch 的那份清单」成为 workspace 根。做法是把 src-tauri 从上层 workspace 的 members移除,并在自己的清单里写一个空 [workspace] 表,让它独立成一个 workspace:

# Cargo.ohos.toml 末尾
[workspace]

这样交换成 ohos 清单后,[patch.crates-io] 就成了这个独立 workspace 的根清单配置,真正生效。代价是它不再属于上层 workspace(需要单独管理依赖),但换来了两套平台依赖互不干扰——符合「侵入式最小」的目标:改动只发生在 crates/agent-mobile 内部。

关键点二:锁文件也要成对

Cargo.lock 决定了实际拉取的版本,只换清单不换锁文件,CI 或别人机器上就会「依赖漂移」。所以锁文件也必须成对管理,并且两边都提交

  • Cargo.ohos.lock 里 pin 的是 tauri/tao/wryfeat/open-harmony 分支 commit,以及 harmony-contrib/openharmony-ability 这类 OHOS 专属依赖。
  • Android 的 Cargo.lock 里则一个 git 依赖都没有,全是 crates.io。

关键点三:幂等、抗中断的构建脚本

构建时替换最容易出问题的是「换到一半被 Ctrl+C」留下中间态。所以 scripts/build-ohos.mjs 花了心思在健壮性上,几个设计点:

1. 三种模式

// package.json
"build:ohos":   "node scripts/build-ohos.mjs cargo tauri ohos build", // swap → 执行 → 恢复
"swap:ohos":    "node scripts/build-ohos.mjs --swap",                 // 只 swap(配合 DevEco Studio)
"restore:ohos": "node scripts/build-ohos.mjs --restore",              // 只恢复 Android

2. 交换 + 恢复,全程幂等

function swapToOhos() {
  renameSync(F.toml, F.tomlBak);     // Cargo.toml    → Cargo.android.toml.bak
  renameSync(F.ohosToml, F.toml);    // Cargo.ohos.toml → Cargo.toml
  renameSync(F.lock, F.lockBak);     // Cargo.lock    → Cargo.android.lock.bak
  renameSync(F.ohosLock, F.lock);    // Cargo.ohos.lock → Cargo.lock
}

function restoreAndroid() {
  // 只有存在 .bak 时才恢复;写回 ohos 文件前先确认当前文件存在
  if (existsSync(F.tomlBak)) {
    if (existsSync(F.toml)) renameSync(F.toml, F.ohosToml); // 把 ohos 清单写回
    renameSync(F.tomlBak, F.toml);
  }
  // ...lock 同理
}

3. 交换前校验 + 自动恢复

  • assertSwapReady():四个文件必须齐全、不能残留上次的 .bak,否则直接报错而不是 rename 到一半崩掉。
  • autoRecoverIfInterrupted():如果检测到上次构建中断残留的 .bak,先幂等恢复再继续——用户永远不用手动 --restore

4. 换完自检

// 交换后的 Cargo.toml 必须带 [patch.crates-io](Harmony 版),否则说明换错了文件
if (!readFileSync(F.toml, "utf8").includes("[patch.crates-io]")) {
  restoreAndroid();
  throw new Error("交换后的 Cargo.toml 不含 [patch.crates-io],已回滚");
}

5. 锁文件写回

构建过程中 cargo 可能更新锁文件;恢复时会把更新后的 Cargo.lock 写回 Cargo.ohos.lock,保证下次 ohos 构建、以及提交到仓库的锁文件都是最新的。

关键点四:一个真实的翻车案例 —— 被覆盖的 Cargo.ohos.toml

开发过程中我踩过一跤:某天发现 Cargo.ohos.toml 悄悄变成了 Android 版的副本,[patch.crates-io] 整个不见了;但 Cargo.ohos.lock 里还老老实实 pin 着 tauri / tao / wry 的 git 分支。清单和锁文件就这么静默不一致了——本地构建会因为自检报错,但光看文件根本看不出哪里不对。

追溯下来,元凶是早期版本的还原逻辑没有幂等守卫

// 早期版本:裸 rename,没有判断 .bak 是否存在
renameSync(F.toml, F.ohosToml);   // 把当前 Cargo.toml 顶进 Cargo.ohos.toml
renameSync(F.tomlBak, F.toml);    // .bak 不存在 → 抛错

如果在「没有处于交换态」的时候误跑了 --restore,它就会先把 Android 版 Cargo.toml 覆盖进 Cargo.ohos.toml,然后才崩在第二行——留下一个被污染的 ohos 清单。

从这个坑里总结出两条经验,都写进了现在的脚本:

  1. 还原必须幂等:只在 .bak 存在时才动文件(上面的 restoreAndroid 就是这么写的),这样在干净状态误跑 --restore 是 no-op。
  2. 交换前 fail-fastassertSwapReady 在 rename 之前就检查 Cargo.ohos.toml 是否含 [patch.crates-io],缺了就立刻报「可能被覆盖成 Android 版」,而不是等交换后自检再回滚。

还有一条更通用的心法:锁文件是「事实来源」。清单里的 patch 丢了之后,我是靠 Cargo.ohos.lock 里的 git source 反推出「该 patch 哪些 crate」的——清单会被误改,锁文件不会说谎。

两套构建工作流

命令行构建(CI 友好),一条命令搞定「换 → 建 → 还原」:

pnpm build:ohos

DevEco Studio 构建,因为 hvigor 会自己去调 cargo tauri ohos dev-eco-studio-script,脚本没法全程包裹,所以要手动先换、后还原:

pnpm swap:ohos       # 切到 HarmonyOS 清单
# 在 DevEco Studio 里打开 src-tauri/gen/ohos 构建
pnpm restore:ohos    # 还原 Android 清单

对应地,平台工程也各归各位:Android 的 gen/android 已提交(用于 CI 复现构建),HarmonyOS 的是 gen/ohos(hvigor 工程,AppScope / entry / hvigorfile.ts 那一套)。

前置环境的一句话备忘

两个平台各自的工具链差异,也能看出这套方案「隔离」的价值:

# Rust 目标
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android  # Android
rustup target add aarch64-unknown-linux-ohos x86_64-unknown-linux-ohos                                   # HarmonyOS

# tauri CLI 走 open-harmony 分支(才能识别 ohos 子命令)
cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/open-harmony

版本号与 CI

发布版本号以 git tag 为事实来源v0.1.3 → Android versionName=0.1.3versionCodemajor*1000000 + minor*1000 + patch 推导),不手改 tauri.conf.json。CI 拆成两条:check(前端类型检查 + 构建 + lint,Rust 侧 cargo check --tests)和 release(打 tag / 手动触发时解码签名 keystore、构建签名 APK/AAB 发 GitHub Release)。

小结

回头看,这套方案的本质是用「构建时的文件切换」绕开「Cargo 无法按平台声明依赖」的硬约束,同时把侵入范围锁死在 crates/agent-mobile 一个 crate 里。几个可以复用的点:

  1. [patch] 只认 workspace 根 —— 想让 patch 生效,就得让承载它的清单成为根,这是整个方案成立的前提。
  2. 清单和锁文件要成对、成对提交 —— 否则「本地能跑、CI 崩」这种依赖漂移会很难查。
  3. 替换类脚本一定要幂等 + 抗中断 —— rename 一半崩溃是这类方案最真实的失败模式,校验、自检、自动恢复一个都不能少。
  4. 平台工程产物各归各位 —— gen/androidgen/ohos 分开管理,提交/忽略策略也各不同。

如果你也在给 Tauri 项目接 HarmonyOS,或者在做任何「一套代码、多套 Cargo 依赖」的工程,希望这篇能帮你少走点弯路。

CC BY-NC-SA 4.0

非商业转载请注明出处,商业转载请联系作者获得授权。

For non-commercial use, please indicate the source. For commercial use, please contact the author for authorization.

View license

评论