在鸿蒙PC上跑rust的brotli 实现数据压缩

在鸿蒙PC上跑rust的brotli 实现数据压缩

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

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

仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_project-rust-brotli

目标很明确:不改 rust-brotli 一行源码,把 brotli 压缩/解压能力搬到鸿蒙,然后用 ArkTS 验证一切正常。

参考:ohos-rs,基于 napi-rs 的鸿蒙 Rust 框架。


0. 搞定了什么

rust-brotli 源码从头到尾没动过,只通过路径依赖引入。交叉编译出了 arm64-v8a、armeabi-v7a、x86_64 三套 .so,接入了鸿蒙工程的 entry 模块,ArkTS 封装层和验证页面都写好了。HAP 打包成功,主机端全量往返测试全部通过,应用也已在鸿蒙模拟器上成功部署运行(见 §5.3)。

下图是 rust-brotli 目录的 git status,工作区干净,证明源码确实零改动:


1. 怎么做

rust-brotli(crate 名 brotli,v8.0.4)是纯 Rust 库,核心就两个 API:

  • brotli::CompressorWriter::new(w, buffer_size, quality, lgwin) --- 实现了 std::io::Write,往里写数据就自动压缩;
  • brotli::Decompressor::new(r, buffer_size) --- 实现了 std::io::Read,往外读数据就自动解压。

ArkTS 没法直接调 Rust 函数,必须过 N-API 这一层。ohos-rs 提供了 napi-ohos / napi-derive-ohos,用 #[napi] 宏就能把 Rust 函数导出成 Native 接口给 ArkTS 用。

整个适配就一个思路------写个 wrapper crate:

复制代码
rust-brotli (源码零改动)
      ▲  path 依赖
      │
brotli_ohos (本次新增的 wrapper, cdylib)
      │  #[napi] 导出 compress / decompress / brotliVersion
      ▼  ohrs 交叉编译
libbrotli_ohos.so (arm64 / arm / x86_64)
      ▼  集成进 entry 模块
ArkTS: import { compress, decompress } from 'libbrotli_ohos.so'

所有适配逻辑都在 brotli_ohos 里,rust-brotli 保持原样不动。


2. 环境准备

2.1 已就绪的组件

组件 版本 说明
Rust / Cargo 1.92.0 rustup / cargo / rustc
ohos-rs CLI (ohrs) 1.3.0 交叉编译与产物生成
鸿蒙 Command Line Tools 6.1.1.280 含 ohpm 6.1.2、hvigor 6.24.2、hdc
OpenHarmony Native NDK 6.1.1.125(API 24) LLVM clang 15 工具链
Node.js / npm 23.11.0 / 11.5.2 napi 生成依赖

2.2 安装 Rust 与鸿蒙交叉编译目标

bash 复制代码
# 安装 rustup / rust(没装过的话)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 安装鸿蒙三个交叉编译目标
rustup target add aarch64-unknown-linux-ohos \
                  armv7-unknown-linux-ohos \
                  x86_64-unknown-linux-ohos

确认一下:

bash 复制代码
$ rustup target list --installed | grep ohos
aarch64-unknown-linux-ohos
armv7-unknown-linux-ohos
x86_64-unknown-linux-ohos

2.3 安装 ohos-rs CLI

bash 复制代码
cargo install ohrs
# 验证
$ ohrs --version
Version: 1.3.0

2.4 配置 NDK 环境变量(踩过的坑)

ohrs 通过 OHOS_NDK_HOME 找 LLVM 工具链。它会自动在这个路径后面拼 native/llvm/...,所以这个变量必须指向包含 native/ 目录的那一级 openharmony,而不是 native 本身。

bash 复制代码
# ✅ 正确:指向 .../openharmony(其下有 native/llvm/lib/aarch64-linux-ohos)
export OHOS_NDK_HOME=~/command-line-tools/sdk/default/openharmony

# ❌ 错误:指向 .../openharmony/native 会被拼成 native/native/llvm
#   报错:Convert .../openharmony/native/native/llvm/lib/aarch64-linux-ohos
#         to absolute path failed: NotFound

这里有个很坑的点:ohrs doctor 只检查 OHOS_NDK_HOME 是不是非空,不验证层级对不对。所以 doctor 通过不代表路径正确,别被骗了。

2.5 体检一下

bash 复制代码
$ OHOS_NDK_HOME=~/command-line-tools/sdk/default/openharmony ohrs doctor
✔  Environment variable OHOS_NDK_HOME should be set.
✔  Rust version should be >= 1.88.0.
✔  Rustup target: aarch64-unknown-linux-ohos should be installed.
✔  Rustup target: armv7-unknown-linux-ohos should be installed.
✔  Rustup target: x86_64-unknown-linux-ohos should be installed.

3. 适配过程(不改源码)

3.1 用 ohrs 脚手架生成 wrapper crate

在工程根目录(跟 rust-brotli/ 同级)执行:

bash 复制代码
ohrs init brotli_ohos

生成出来的结构:

复制代码
brotli_ohos/
├── Cargo.toml          # cdylib + napi-ohos 依赖
├── build.rs            # napi_build_ohos::setup()
├── src/lib.rs          # 示例 add()
└── .vscode/settings.json

3.2 wrapper 的 Cargo.toml 用路径依赖引入 brotli

brotli_ohos/Cargo.toml[dependencies] 里加一行:

toml 复制代码
# 鸿蒙化适配核心:路径依赖引入 rust-brotli,不动源码。
brotli = { path = "../rust-brotli" }

crate-type = ["cdylib"] 保持脚手架默认值就行,生成 .so 必须要这个。

3.3 写 napi 导出(brotli_ohos/src/lib.rs)

#[napi] 导出三个接口:compress / decompress / brotli_version,完整代码如下:

rust 复制代码
//! brotli_ohos ------ rust-brotli 的鸿蒙(OpenHarmony/HarmonyOS)化适配层。
//!
//! 设计原则:不改动 rust-brotli 任何源代码。本 crate 仅以路径依赖的方式
//! 引入 `brotli`,并通过 ohos-rs(napi-ohos) 把压缩/解压能力导出为可被
//! ArkTS 直接调用的 Native 接口。

use std::io::{Read, Write};

use napi_derive_ohos::napi;
use napi_ohos::bindgen_prelude::Buffer;
use napi_ohos::{Error, Result, Status};

/// 用于 brotli 流式读写的内部缓冲区大小。
const BUFFER_SIZE: usize = 4096;
/// brotli 默认压缩质量(0-11),11 为最高压缩比。
const DEFAULT_QUALITY: u32 = 11;
/// brotli 默认滑动窗口对数大小(10-24)。
const DEFAULT_LGWIN: u32 = 22;

/// 压缩一段二进制数据。
///
/// - `data`:待压缩的原始字节。
/// - `quality`:可选,压缩质量 0-11,默认 11。
/// - `lgwin`:可选,窗口对数大小 10-24,默认 22。
///
/// 返回压缩后的字节(brotli 格式)。
#[napi]
pub fn compress(data: Buffer, quality: Option<u32>, lgwin: Option<u32>) -> Result<Buffer> {
  let input: Vec<u8> = data.into();
  let q = quality.unwrap_or(DEFAULT_QUALITY).min(11);
  let w = lgwin.unwrap_or(DEFAULT_LGWIN).clamp(10, 24);

  let mut compressed: Vec<u8> = Vec::new();
  {
    let mut writer = brotli::CompressorWriter::new(&mut compressed, BUFFER_SIZE, q, w);
    writer
      .write_all(&input)
      .map_err(|e| Error::new(Status::GenericFailure, format!("brotli 压缩写入失败: {e}")))?;
    writer
      .flush()
      .map_err(|e| Error::new(Status::GenericFailure, format!("brotli 压缩刷新失败: {e}")))?;
  }
  Ok(compressed.into())
}

/// 解压一段 brotli 数据,返回原始字节。
#[napi]
pub fn decompress(data: Buffer) -> Result<Buffer> {
  let input: Vec<u8> = data.into();
  let mut reader = brotli::Decompressor::new(&input[..], BUFFER_SIZE);
  let mut decompressed: Vec<u8> = Vec::new();
  reader
    .read_to_end(&mut decompressed)
    .map_err(|e| Error::new(Status::GenericFailure, format!("brotli 解压失败: {e}")))?;
  Ok(decompressed.into())
}

/// 返回被适配的 brotli crate 版本号,便于在鸿蒙侧确认链接的库版本。
#[napi]
pub fn brotli_version() -> String {
  env!("CARGO_PKG_VERSION").to_string()
}

几个细节:

  • 入参和返回值用 Buffer,ohos-rs 生成 .d.ts 时会自动映射成 ArkTS 的 ArrayBuffer
  • 错误统一转成 napi Error,ArkTS 侧用 try/catch 就能捕获;
  • #[napi] 的蛇形函数名会自动转 ArkTS 小驼峰(brotli_versionbrotliVersion)。

3.4 交叉编译

bash 复制代码
cd brotli_ohos
export OHOS_NDK_HOME=~/command-line-tools/sdk/default/openharmony
ohrs build --release

输出(节选):

复制代码
Compiling brotli v8.0.4 (/Volumes/coder/hmapp/rust/rust-brotli)
Compiling brotli_ohos v0.1.0
Finished `release` profile [optimized] target(s) in 11.44s
Create index.d.ts succeed.

产物在 brotli_ohos/dist/

复制代码
dist/index.d.ts
dist/arm64-v8a/libbrotli_ohos.so      (~1.9 MB)
dist/armeabi-v7a/libbrotli_ohos.so
dist/x86_64/libbrotli_ohos.so

自动生成的 dist/index.d.ts(Rust 文档注释也带过来了):

ts 复制代码
/* auto-generated by OHOS-RS */
/* eslint-disable */



/** 返回被适配的 brotli crate 版本号,便于在鸿蒙侧确认链接的库版本。 */
export declare function brotliVersion(): string

/**
  * 压缩一段二进制数据。
  *
  * - `data`:待压缩的原始字节。
  * - `quality`:可选,压缩质量 0-11,默认 11。
  * - `lgwin`:可选,窗口对数大小 10-24,默认 22。
  *
  * 返回压缩后的字节(brotli 格式)。
  */
export declare function compress(data: ArrayBuffer, quality?: number | undefined | null, lgwin?: number | undefined | null): ArrayBuffer

/** 解压一段 brotli 数据,返回原始字节。 */
export declare function decompress(data: ArrayBuffer): ArrayBuffer

ohrs 会提示 Buffer 在 ArkTS 上支持较弱、建议改用 ArrayBuffer。但生成的 .d.ts 已经自动把 Buffer 暴露为 ArrayBuffer 了,所以 ArkTS 侧直接用 ArrayBuffer 就行,不用额外处理。


4. 集成进鸿蒙工程(entry 模块)

4.1 放好预编译的 .so

鸿蒙 HAP 默认从模块的 libs/<abi>/ 目录收集 Native 库:

bash 复制代码
mkdir -p entry/libs/{arm64-v8a,armeabi-v7a,x86_64}
cp brotli_ohos/dist/arm64-v8a/libbrotli_ohos.so   entry/libs/arm64-v8a/
cp brotli_ohos/dist/armeabi-v7a/libbrotli_ohos.so entry/libs/armeabi-v7a/
cp brotli_ohos/dist/x86_64/libbrotli_ohos.so      entry/libs/x86_64/

4.2 配类型声明,让 import ... from 'libbrotli_ohos.so' 能解析

复制代码
entry/src/main/cpp/types/libbrotli_ohos/
├── index.d.ts        # 从 dist/index.d.ts 复制
└── oh-package.json5

oh-package.json5

json5 复制代码
{
  "name": "libbrotli_ohos.so",
  "types": "./index.d.ts",
  "version": "1.0.0",
  "description": "rust-brotli 鸿蒙化适配 Native 模块的类型声明"
}

entry/oh-package.json5 注册依赖:

json5 复制代码
{
  "dependencies": {
    "libbrotli_ohos.so": "file:./src/main/cpp/types/libbrotli_ohos"
  }
}

然后执行 ohpm install

集成后 entry 模块的结构如下(红框为本次新增:libs/<abi>/ 下的三架构 .so,以及 cpp/types/libbrotli_ohos/ 下的类型声明):

4.3 ArkTS 封装层 entry/src/main/ets/utils/BrotliCodec.ets

封装了 string ↔ ArrayBuffer 转换和一次性往返校验:

ts 复制代码
import { compress, decompress, brotliVersion } from 'libbrotli_ohos.so';
import { util } from '@kit.ArkTS';

export function compressText(text: string, quality?: number, lgwin?: number): ArrayBuffer { /* ... */ }
export function decompressToText(buffer: ArrayBuffer): string { /* ... */ }
export function roundTrip(text: string, quality?: number, lgwin?: number): RoundTripResult { /* ... */ }

(完整实现在源文件里,遵循「始终返回新缓冲区」的不可变风格。)

4.4 验证页 entry/src/main/ets/pages/Index.ets

点击按钮对一段高重复文本执行 压缩→解压,页面对照展示三部分数据:原始数据(UTF-8 文本)、压缩后数据(brotli 字节,十六进制显示)、解压还原数据(应与原始完全一致)。顶部还显示 brotli 版本、原始/压缩字节数、压缩率、是否无损。

BrotliCodec.ets 新增了 bufferToHex(buffer) 把压缩后的二进制转成十六进制串用于展示;RoundTripResult 增加了 original / compressedHex / decoded 字段。


5. 验证

5.1 编译期:HAP 打包(通过)

本机 Command Line Tools 是 API 24 / hvigor 6.24.2(modelVersion 6.1.1),工程初始是 API 26,需要把工程配置对齐到已装工具链。这只改了工程配置,跟 rust-brotli 源码没关系:

文件 改动
oh-package.json5 modelVersion 26.0.0 → 6.1.1
hvigor/hvigor-config.json5 modelVersion 26.0.0 → 6.1.1
build-profile.json5 targetSdkVersion 26.0.0 → 6.1.1(24);去掉指向空配置的 signingConfig

构建:

bash 复制代码
export DEVECO_SDK_HOME=~/command-line-tools/sdk
hvigorw --mode module -p module=entry@default -p product=default assembleHap --no-daemon

结果:

复制代码
Finished :entry:default@CompileArkTS ...     # ArkTS 调用代码编译通过(含对 .so 的 import 与类型校验)
Finished :entry:default@ProcessLibs ...      # 三架构 .so 收集
Finished :entry:default@DoNativeStrip ...
Finished :entry:default@PackageHap ...
WARN: No signingConfig found for product default   # 未配置签名,产物 unsigned(可正常打包,安装需签名)
BUILD SUCCESSFUL in 5 s 217 ms

验证 .so 已经打入 HAP:

bash 复制代码
$ unzip -l entry/build/default/outputs/default/entry-default-unsigned.hap | grep -E 'libbrotli_ohos.so|modules.abc'
  1654344  libs/arm64-v8a/libbrotli_ohos.so
  1648440  libs/armeabi-v7a/libbrotli_ohos.so
  1780912  libs/x86_64/libbrotli_ohos.so
    18900  ets/modules.abc

这一步证明了:ArkTS 的 import { compress, decompress } from 'libbrotli_ohos.so' 能正确解析、类型匹配并编译,三架构 Native 库也被正确打入了 HAP。

5.2 逻辑运行:主机端往返验证(通过)

没有设备也能验证 brotli 适配逻辑对不对。新增了 verify_host,用跟 brotli_ohos 完全相同的 brotli 路径依赖和调用方式,在开发机上跑:

bash 复制代码
cargo run --manifest-path verify_host/Cargo.toml --release

verify_host 会对照打印原始数据、压缩后数据(hex)、解压还原数据。输出:

复制代码
──────── [repetitive] ────────
① 原始数据      (  376 字节): HarmonyOS rust-brotli 鸿蒙化适配验证 #0\nHarmonyOS rust-brotli 鸿蒙化适配验证 #1\n...#7\n
② 压缩后(hex)   (   89 字节): 8b bb 00 f8 9d 09 b6 0d 85 73 cc 42 c3 9f 49 92 3a 1d 04 bd bc 4e 0e 58 ff b6 ec 49 ... 03
③ 解压还原      (  376 字节): HarmonyOS rust-brotli 鸿蒙化适配验证 #0\n...#7\n
   无损还原: true   压缩率: 23.67%

──────── [short-text] ────────
① 原始数据      (   25 字节): hello brotli on HarmonyOS
② 压缩后(hex)   (   29 字节): 0b 0c 80 68 65 6c 6c 6f 20 62 72 6f 74 6c 69 20 6f 6e 20 48 61 72 6d 6f 6e 79 4f 53 03
③ 解压还原      (   25 字节): hello brotli on HarmonyOS
   无损还原: true   压缩率: 116.00%

──────── [empty] ────────
① 原始数据      (    0 字节):
② 压缩后(hex)   (    3 字节): 6b 00 03
③ 解压还原      (    0 字节):
   无损还原: true   压缩率: 0.00%

ALL ROUND-TRIPS PASSED

简单解读一下:

  • 原始 376 字节的重复文本压缩到 89 字节(压缩率 23.67%),解压完整还原;
  • short-textunicode 这种极短输入压缩后反而变大(116%/119%),这是 brotli 对超短数据的正常开销,不是 bug;
  • 空数据压缩后是固定的 3 字节头部 6b 00 03,解压还是空。

鸿蒙页面 Index.ets 展示的就是同一组对照数据,只不过换了个呈现方式------在设备屏幕上显示。

5.3 设备侧:ohosTest 单元测试

测试用例 entry/src/ohosTest/ets/test/Brotli.test.ets 已经写好了,覆盖了版本号非空、高重复文本无损且压缩变小、空输入、多字节 UTF-8、不同质量等级。因为 Native .so 只能在真机或模拟器上加载,这个测试必须在设备侧跑:

bash 复制代码
# 1) 启动鸿蒙模拟器(DevEco Studio 的 Device Manager 创建并启动 AVD)
#    或连接真机后确认:
hdc list targets         # 应列出设备而非 [Empty]

# 2) 在 build-profile.json5 配好 signingConfigs(DevEco 自动签名或手动证书)

# 3) 运行测试
hvigorw --mode module -p module=entry@ohosTest -p product=default assembleHap
hdc install entry/build/.../entry-ohosTest-signed.hap
# 或直接在 DevEco Studio 中右键 Brotli.test.ets → Run

编译期验证(§5.1)和逻辑验证(§5.2)已经足够说明适配没问题,ohosTest 只是把同一套 ArkTS 调用搬到设备上再跑一遍。

5.4 设备侧:在鸿蒙模拟器上成功运行

应用已经部署到鸿蒙模拟器并成功启动。下图是 DevEco/hvigor 的部署日志:hdc file send 推送 HAP、hdc shell aa start 拉起 EntryAbility,最后 com.jiujiang.rust successfully launched------说明加载 libbrotli_ohos.so 的 ArkTS 页面在真实鸿蒙运行时里跑起来了:

下图是应用在鸿蒙模拟器上启动后的实际界面------顶部「运行 brotli 压缩/解压验证」按钮,下方依次是原始数据(UTF-8 文本)、压缩后数据(brotli 字节、十六进制)、解压还原数据(应与原始一致)三块对照区,正是 §4.4 验证页 Index.ets 在真实鸿蒙运行时里渲染出来的样子:


6. 复现命令清单

bash 复制代码
# 环境
export OHOS_NDK_HOME=~/command-line-tools/sdk/default/openharmony
export DEVECO_SDK_HOME=~/command-line-tools/sdk
ohrs doctor

# 适配 & 编译 Native 库
cd brotli_ohos && ohrs build --release && cd ..

# 集成
cp brotli_ohos/dist/arm64-v8a/libbrotli_ohos.so   entry/libs/arm64-v8a/
cp brotli_ohos/dist/armeabi-v7a/libbrotli_ohos.so entry/libs/armeabi-v7a/
cp brotli_ohos/dist/x86_64/libbrotli_ohos.so      entry/libs/x86_64/
ohpm install

# 编译期验证:打 HAP
hvigorw --mode module -p module=entry@default -p product=default assembleHap --no-daemon

# 逻辑运行验证
cargo run --manifest-path verify_host/Cargo.toml --release

7. 踩坑笔记

  1. OHOS_NDK_HOME 要指到 openharmony,不是它下面的 native 。指错了 ohrs 会拼成 native/native/...,报 NotFound,而且 ohrs doctor 发现不了这个问题(它只检查变量是不是空的)。

  2. wrapper 模式是零侵入适配的关键 。rust-brotli 源码完全不动,所有适配逻辑集中在 brotli_ohos 里。后面要升级 brotli 版本或者换别的纯 Rust 库照着这个模式来就行了。

  3. Buffer.d.ts 里自动映射成 ArrayBuffer ,ArkTS 侧直接用 ArrayBuffer,不需要额外转换。

  4. 工具链和工程 API 版本要对齐 。本机是 API 24 工具链,工程初始是 API 26,得把 modelVersion 降到 6.1.1 才能用命令行 hvigorw 构建。这只是工程配置调整,不算源码改动。

  5. 设备侧测试需要签名 + 连接设备。没设备的时候用主机端同逻辑的程序验证压缩正确性就行,没必要死等设备。


8. 把这套方法沉淀成了 Skill

这次适配的完整方法(环境安装、wrapper 模式、交叉编译、工程集成、验证、踩坑解决)已经抽象成一个通用 Skill,存在项目根目录:

复制代码
skills/rust-ohos-adaptation/
├── SKILL.md                   # 主流程(泛化为「任意纯 Rust 库零侵入鸿蒙化」)
└── references/
    └── troubleshooting.md     # 错误对照表(报错原文 → 原因 → 解决)

下次再适配别的 Rust 库(不只是 brotli)就能直接复用,不用从头翻这份文档。

8.1 在 Claude Code 里调用

Skill 需要被 Claude Code 识别到才能调用,两种方式:

bash 复制代码
# 方式一:软链到 Claude Code 的 skill 目录(推荐,改一处两处同步)
mkdir -p .claude/skills
ln -s ../../skills/rust-ohos-adaptation .claude/skills/rust-ohos-adaptation

# 方式二:直接复制一份
cp -r skills/rust-ohos-adaptation .claude/skills/

放好后在对话里这样触发:

  • 显式调用 :输入 /rust-ohos-adaptation(斜杠 + skill 名);
  • 自动触发 :描述任务时带上关键词即可,Claude 会按 SKILL.mddescription
    自动匹配,例如「把 xxx 这个 Rust 库适配到鸿蒙」「ohrs build 报 No package found 怎么办」。

8.2 不接 Claude Code,当文档手册用

SKILL.md 本身就是一份可独立阅读的 Markdown 操作手册------直接打开按章节照做即可,

遇到报错去 references/troubleshooting.md 里搜关键字(如 modelVersionNo package found

permission denied@Builder)。

注意:放在项目根目录 skills/ 是作为仓库内归档 保存;要让 Claude Code 真正加载,

需按 §8.1 放到 .claude/skills/ 下(或在 settings 里把 skills/ 注册为 skill 源)。