2026-08-18 晚,基于 tauri 官方仓库 feat/open-harmony 分支(tauri 2.11.5,crates.io 官方最新版)重新移植 cc-switch 到 HarmonyOS PC。上一篇实录里用的社区 fork 停在 tauri 2.8.5,把整个依赖树锁死在两年前;这一次,插件全部用最新版,一个都不用降。


项目地址*:https://atomgit.com/qq8864/cc-switch-harmonyos

更多交流学习,欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目https://atomgit.com/OpenHarmonyPCDeveloper

猫哥的博客https://blog.csdn.net/qq8864

在这里插入图片描述

起因:旧 fork 的版本债

第一次移植 cc-switch 到鸿蒙 PC 时,用的是社区维护的 fork(基于 tauri 2.8.5)。那个 fork 能跑,但代价很重:

tauri 2.8.5 → tauri-plugin-log 锁 =2.7.1、dialog 锁 =2.4.2、updater 锁 =2.9.0 …

上游插件早就进化到 2.9/2.10 了,我们却要为了兼容 2.8.x 的 API 面,把每个插件钉回老版本——这就是"版本债":能跑,但整个项目的基础设施停在两年前,以后想升级上游代码,先得还债。

于是我决定从官方 fork 一份 tauri,在 feat/open-harmony 分支上重新移植。结果发现:官方自己已经在维护 OpenHarmony 支持了

仓库 分支 版本
tauri-apps/tauri(经 yangyongzhen fork) feat/open-harmony tauri 2.11.5(= crates.io 官方最新)
tauri-apps/wry feat/open-harmony 0.56.0
tauri-apps/tao feat/open-harmony 0.36.0
tauri-apps/cargo-mobile2 feat/ohos 0.22.4

wry、tao、cargo-mobile2 三个底层库的 OHOS 支持分支都在 tauri-apps 官方账号下——这不是民间 hack,是官方生态的一部分。


成果速览

验证项 结果
Rust 交叉编译 libcc_switch_lib.so(aarch64 ELF,strip 后 23.7MB,旧 fork 是 33MB)
HAP entry-default-signed.hap(34.8MB,旧 fork 39.8MB,签名版)
真机 HUAWEI MateBook Pro HAD-W32,API 24,aarch64,hdc install + aa start 成功
进程 主进程 + GPU + render 三进程稳定存活
后端 数据库迁移、189 条模型定价、Copilot/Codex/Xai OAuth、全局代理全部初始化,0 错误
前端 tauri://localhost/assets/... 加载(生产模式),React 正常挂载,0 panic / 0 error_boundary
插件 log 2.9.0 / store 2.4.4 / process 2.3.1 / dialog 2.7.2 / fs 2.5.1 / opener 2.5.4 / updater 2.10.1 / deep-link 2.4.9 / window-state 2.4.1 / single-instance 2.4.3,全部 crates.io 最新

体积还变小了 10MB。新代码,新体积,何乐不为。


原理:还是那套,前端零改动

cc-switch (Rust + React)
  → libcc_switch_lib.so(napi-ohos 桥接,mobile_entry_point 宏展开)
  → OHOS ArkWeb WebView(渲染原前端,withGlobalTauri 全局桥)
  → HAP(hvigor 打包,签名后 hdc 部署)

React 前端原样打包,window.__TAURI__ 全局桥提供 IPC。Rust 后端交叉编译成 .so,由 RustAbility(@ohos-rs/ability)加载。原理没变,变的是依赖图和踩坑清单。


第一关:依赖解析,patch 的完全体

上次移植最大的坑是 links = "Tauri" 冲突(路径依赖不被解析器当作插件版本候选),解法是用 [patch.crates-io]。这次 fork 换新,同样的机制,但要 patch 的东西多了三样:

1. tauri 系列:path patch(和上次一样)

[patch.crates-io]
tauri = { path = "D:/tauri-ohos-new/crates/tauri" }
tauri-build = { path = "D:/tauri-ohos-new/crates/tauri-build" }
tauri-macros = { path = "D:/tauri-ohos-new/crates/tauri-macros" }
tauri-codegen = { path = "D:/tauri-ohos-new/crates/tauri-codegen" }
tauri-utils = { path = "D:/tauri-ohos-new/crates/tauri-utils" }
tauri-runtime = { path = "D:/tauri-ohos-new/crates/tauri-runtime" }
tauri-runtime-wry = { path = "D:/tauri-ohos-new/crates/tauri-runtime-wry" }

2. wry / tao:git patch,而且要钉版本

这是新旧 fork 最大的差异。旧 fork 直接把 wry/tao 写成 git 依赖(wry = { git = "https://github.com/richerfu/wry" }),自带 OHOS 实现;新 fork 回到官方风格——tauri-runtime-wry 里声明的是 registry 版本:

wry = { version = "0.56.0", ... }
tao = { version = "0.36.0", ... }

而 OHOS 支持在 tauri-apps 官方仓库的分支里。问题来了:fork 根 Cargo.toml 里的 [patch] 只对 fork 自己的 workspace 生效,不跨工程继承。所以 cc-switch 必须自己补 patch:

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

光 patch 还不够。registry 上 wry 已经发到 0.56.1,而分支是 0.56.0——需求 ^0.56.0 会解析到更高的 0.56.1,绕过 patch 回到 registry 版(没有 OHOS 支持)。必须显式钉死:

[dependencies]
wry = { version = "=0.56.0", default-features = false }

这种"patch 版本低于 registry 最新"的坑,以后官方分支一更新就没了,但当下必须钉。

3. openharmony-ability:本地 path patch,钉 0.3.0

最隐蔽的一个。wry 的 OHOS 段依赖:

openharmony-ability = { git = "https://github.com/harmony-contrib/openharmony-ability.git", features = ["webview"] }

注意 features = ["webview"]。而 openharmony-ability 这个仓库后来大重构了:main 分支已经变成 1.0.0-beta.1,把 webview 拆成了独立的 plugin-webview crate,主 crate 上根本没有 webview feature——于是:

failed to select a version for `openharmony-ability`
package `wry` depends on `openharmony-ability` with feature `webview`
but `openharmony-ability` does not have that feature

解法:钉回 fork 的 Cargo.lock 里用的那个 commit(295a276,版本 0.3.0,当时 webview 还在主 crate 里)。但 cargo 有个规则:patch 不能指向同一个 source(不允许 git + rev 覆盖同一个 git URL)。绕法是把 0.3.0 克隆到本地,用 path patch——source 不同,合法:

[patch."https://github.com/harmony-contrib/openharmony-ability.git"]
openharmony-ability = { path = "D:/openharmony-ability-030/crates/ability" }
openharmony-ability-derive = { path = "D:/openharmony-ability-030/crates/derive" }

(这是本机构建的硬依赖,换机器要重拉:git clonegit checkout 295a276。)

4. 插件与 npm 包:全部升到最新

依赖解析通过后,插件版本从"锁定旧版"改成"最新版":

tauri-plugin-log = "2.9.0"
tauri-plugin-store = "2.4.4"
tauri-plugin-dialog = "2.7.2"   # 桌面段
tauri-plugin-updater = "2.10.1" # 桌面段
# …全部最新

然后 npm 侧必须同步——新版 CLI 会校验 Rust crate 与 @tauri-apps/* npm 包的 major/minor 一致,不匹配直接拒绝构建:

Error Found version mismatched Tauri packages.
tauri (v2.11.5) : @tauri-apps/api (v2.8.0)

package.json 里的 @tauri-apps/api 升到 2.11.1、@tauri-apps/cli 升到 2.11.4、各插件 npm 包同步升,再 pnpm install

到这里,cargo update 一次通过——没有任何版本冲突。这就是新 fork 的价值:依赖树和官方最新生态对齐,解析器不再打架


第二关:编译,13 分 26 秒

cargo tauri ohos build -t aarch64(记得 OHOS_HOME / OHOS_NDK_HOME 指向 SDK 根目录)。全量依赖树第一次编译,13m26s,一次通过——比旧 fork 顺畅多了。中间只踩了两个小坑:

坑 A:capabilities 的 platform 值。

我把 ohos.json 里的 platforms 写成了 "OpenHarmony",构建脚本报:

failed to parse JSON: unknown variant `OpenHarmony`,
expected one of `macOS`, `windows`, `linux`, `android`, `iOS`, `openHarmony`

新 fork 的 Target 枚举有 #[serde(rename_all = "camelCase")],OpenHarmony 序列化成 openHarmony——和旧 fork 一样,是我自己想多了,改回去就好。

坑 B:pnpm 的 store 写不进 E 盘。

ERR_PNPM_EPERM Failed to add tarball ... E:\.pnpm-store

E 盘根目录的 pnpm store 没权限,换个位置:pnpm install --store-dir D:/pnpm-store。这是开发机环境问题,和鸿蒙无关,但确实卡了我几分钟。


第三关:HAP 装配与签名

.so 编好后,Windows 上 HAP 装配还得手工来(CLI 的 ohpm/hvigor 在 Windows 上拉不起 .bat),流程和上次一样:

# rawfile 同步(CLI 不会自动做)
cp -r dist/. src-tauri/gen/ohos/entry/src/main/resources/rawfile/

# ohpm 装依赖
cd src-tauri/gen/ohos && ohpm install && cd entry && ohpm install

# 打包 + 签名
node "D:/Program Files/Huawei/DevEco Studio/tools/hvigor/bin/hvigorw.js" \
  assembleHap --mode module -p product=default --no-daemon

签名:这次不用再登华为账号

cargo tauri ohos init 重新生成工程时会清空 signingConfigs。上次的解法是 DevEco Studio GUI 里重新登录华为账号自动生成,这次我发现材料都在 ~/.ohos/config/ 里躺着,而旧的 build-profile.json5 还在 git 历史里——直接把 material 段恢复回去就行:

"signingConfigs": [{
  "name": "default",
  "type": "HarmonyOS",
  "material": {
    "certpath": "C:\\Users\\yang\\.ohos\\config\\default_ohos_eeumV….cer",
    "keyAlias": "debugKey",
    // …p7b / p12 / 两个密码(加密后的 hex)
  }
}]

命令行 hvigorw 直接复用,不需要 GUI。

hvigorfile.ts:还得净化

新模板依然带着 cargo 钩子(dev-eco-studio-script),Windows 上一跑就炸。替换成无钩子版:

import { hapTasks } from '@ohos/hvigor-ohos-plugin';

export default {
  system: hapTasks,
  plugins: []   // cargo 钩子已移除:.so 由 cargo tauri ohos build 手动编译
}

还有一个环境细节:hvigor 的 es2abc/Java 子进程要 cmd.exejava——MSYS bash 的 PATH 转换会让 Node spawn(cmd.exe) 直接 ENOENT。构建前 export 纯 Windows 风格 PATH(含 C:\Windows\System32 和 DevEco 的 jbr\bin)。


第四关:前端,一个被 import 提升坑到的 polyfill

应用跑起来了,但控制台多了一行:

[i18n] Failed to read stored language preference
TypeError: Cannot read properties of null (reading 'getItem')

ArkWeb 在自定义 scheme(tauri://localhost)下 window.localStorage 是 null,这个上次就知道,也写了 polyfill。但这次 polyfill 没拦住 i18n 的报错——原因很微妙:ES module 的 import 是提升的,i18n 模块的顶层代码在 main.tsx 的任何运行时语句之前执行,而 polyfill 写在 main.tsx 的函数体里,晚了一步。

解法:把 polyfill 从 JS 模块挪到 index.html 的内联 <script>——它在 module script 之前执行,谁都比它晚:

<script>
  // ArkWeb 自定义 scheme 下 window.localStorage 为 null。
  // 必须在任何 ES module(i18n 等)执行前安装内存降级实现。
  if (typeof window !== "undefined" && !window.localStorage) {
    var map = new Map();
    Object.defineProperty(window, "localStorage", {
      value: {
        getItem: k => map.has(k) ? map.get(k) : null,
        setItem: (k, v) => map.set(String(k), String(v)),
        removeItem: k => map.delete(k),
        clear: () => map.clear(),
        key: i => Array.from(map.keys())[i] ?? null,
        get length() { return map.size; },
      },
      configurable: true, writable: true,
    });
  }
</script>
<script type="module" src="./main.tsx"></script>

重部署后,[i18n] Failed… 消失。注意:改前端必须走完整链路——vite 重建 dist → cargo tauri ohos build(新 dist 内嵌进 .so)→ 同步 rawfile → 重打包 HAP → 卸载重装(清 ArkWeb 缓存)。


收尾:真机验证

hdc install -r entry-default-signed.hap
hdc shell "power-shell wakeup"        # 屏幕锁定时必做
hdc shell "aa start -a EntryAbility -b com.ccswitch.desktop"

验证三件套:

# 1. 进程:主 + GPU + render 三进程存活
ps -ef | grep ccswitch.desktop

# 2. 后端日志:189 定价、OAuth、代理,0 错误
cat /data/app/el2/100/base/com.ccswitch.desktop/files/.cc-switch/logs/cc-switch.log

# 3. 前端:生产模式资产加载,0 panic / 0 error_boundary
hilog -x | grep -iE 'ARKWEB-CONSOLE|panic|fatal'

全部通过。剩下几条 console 消息都是已知限制的优雅降级:更新检查(updater 在 OHOS 段不注册,ACL 拒绝)、窗口主题切换(返回"当前平台不支持")——都不是崩溃,前端都接住了。


经验总结

  1. 能上官方分支,就别用社区 forkfeat/open-harmony 在 tauri-apps 官方账号下,和 crates.io 最新版同步(2.11.5),插件零降级,依赖解析一次过。
  2. fork 的 workspace patch 不跨工程继承。用别人的 fork,它 patch 的 wry/tao/openharmony-ability 你得自己补;而且 patch 版本低于 registry 最新时,要显式钉 =x.y.z 防绕过。
  3. git 依赖的"无 rev"是定时炸弹。openharmony-ability 的 main 一重构,所有无 rev 的 git 依赖全炸;钉 commit 才是可复现的构建。
  4. ES module import 提升会把"运行时 polyfill"变成"晚到一步"。平台 hack 类代码,能放 index.html 内联就别放 JS 模块。
  5. 签名材料、环境变量、hvigor 钩子这些"一次性配置",全都要沉淀到文档和 git 历史里——重来一遍时它们就是救命稻草。

配套完整技术手册(17 个坑的解法、命令速查)见同目录 cc-switch-to-harmonyos-porting-guide.md(§0 为本次官方 fork 更新记录)。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐