macos上Rust 命令行工具鸿蒙化适配完全攻略
本文以Rust编写的磁盘分析工具dust为例,详细介绍了如何将其适配到OpenHarmony/HarmonyOS平台的完整流程。主要内容包括:1) 开发环境配置,包括Rust工具链安装和OpenHarmony SDK设置;2) 项目结构重构,将核心逻辑与平台代码分离;3) 平台抽象层实现,通过feature flag区分普通Unix和OpenHarmony系统。文章重点解决了目标平台识别、交叉编译
以 dust(磁盘使用分析工具)为例,详解如何将 Rust CLI 工具适配到 OpenHarmony/HarmonyOS 平台
前言
随着鸿蒙生态的快速发展,越来越多的开发者希望将现有的命令行工具移植到鸿蒙系统。本文以 dust(一个用 Rust 编写的磁盘使用分析工具)为例,详细介绍从环境配置到构建部署的完整流程,并记录适配过程中遇到的所有问题及解决方案。
原项目信息
| 项目 | 说明 |
|---|---|
| 项目名称 | dust (du-dust) |
| 项目描述 | 一个更直观的磁盘使用分析工具 |
| 技术栈 | Rust |
| 主要依赖 | clap, rayon, terminal_size, lscolors |
适配目标
将 dust 核心功能适配到 OpenHarmony/HarmonyOS 平台,支持两种使用方式:
- HNP 原生命令行:在鸿蒙终端直接使用
- NAPI 绑定:供 ArkTS/JS 应用调用
适用场景
- 将 Rust CLI 工具移植到鸿蒙系统
- 为鸿蒙应用提供原生 Rust 库支持
- 了解 ohos-rs 框架的使用方法
一、开发环境配置
1.1 基础环境要求
| 工具 | 版本要求 | 用途 |
|---|---|---|
| Rust | 1.77+ | 编程语言工具链 |
| DevEco Studio | NEXT Beta1 (5.0.3.100)+ | 鸿蒙开发 IDE |
| OpenHarmony SDK | API 12+ | 交叉编译工具链 |
1.2 安装 Rust 工具链
# 安装 Rust(如果尚未安装)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 添加 OpenHarmony 目标平台
rustup target add aarch64-unknown-linux-ohos # ARM64 设备
rustup target add armv7-unknown-linux-ohos # ARM32 设备
rustup target add x86_64-unknown-linux-ohos # x86_64 模拟器
# 验证安装
rustup target list | grep ohos
输出示例:
aarch64-unknown-linux-ohos (installed)
armv7-unknown-linux-ohos (installed)
x86_64-unknown-linux-ohos (installed)
1.3 配置 OpenHarmony SDK
-
下载 DevEco Studio:从华为开发者官网下载并安装
-
安装 SDK:在 DevEco Studio 中,通过
Settings > SDK下载 Native SDK -
配置环境变量:
# 添加到 ~/.bashrc 或 ~/.zshrc
export OHOS_NDK_HOME=/path/to/ohos-sdk/native
export PATH="$OHOS_NDK_HOME/llvm/bin:$PATH"
# 使配置生效
source ~/.bashrc
- 验证配置:
echo $OHOS_NDK_HOME
which aarch64-unknown-linux-ohos-clang
1.4 创建 Cargo 交叉编译配置
在项目根目录创建 .cargo/config.toml:
# .cargo/config.toml
[target.aarch64-unknown-linux-ohos]
linker = "aarch64-unknown-linux-ohos-clang"
ar = "llvm-ar"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.armv7-unknown-linux-ohos]
linker = "armv7-unknown-linux-ohos-clang"
ar = "llvm-ar"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.x86_64-unknown-linux-ohos]
linker = "x86_64-unknown-linux-ohos-clang"
ar = "llvm-ar"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
二、项目结构重构
2.1 为什么需要重构?
原始的 dust 项目是一个单体 CLI 应用,所有代码都在一个 crate 中。为了适配鸿蒙平台,我们需要:
- 代码复用:核心逻辑可被 CLI 和 NAPI 模块共享
- 平台隔离:平台特定代码集中管理
- 独立构建:CLI 和 NAPI 库可以独立构建
2.2 目标结构
dust/
├── Cargo.toml # 工作空间配置
├── .cargo/
│ └── config.toml # 交叉编译配置
├── scripts/
│ ├── build-hnp.sh # HNP 构建脚本
│ └── build-ohos.sh # NAPI 库构建脚本
├── dust-core/ # 核心库(平台无关)
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # 库入口
│ ├── node.rs # 文件节点
│ ├── scanner.rs # 扫描器 API
│ ├── types.rs # 类型定义
│ ├── utils.rs # 工具函数
│ ├── walker.rs # 目录遍历
│ └── platform/ # 平台抽象层
│ ├── mod.rs
│ ├── unix.rs
│ ├── windows.rs
│ └── ohos.rs # OpenHarmony 实现
├── dust-cli/ # CLI 应用
│ ├── Cargo.toml
│ ├── build.rs
│ └── src/
│ └── main.rs
└── dust-ohos/ # NAPI 绑定(可选)
├── Cargo.toml
├── build.rs
├── index.d.ts # TypeScript 类型定义
├── examples/
│ └── usage.ets # ArkTS 使用示例
└── src/
├── lib.rs # NAPI 导出
├── types.rs # JS 类型转换
└── error.rs # 错误处理
2.3 创建工作空间配置
根 Cargo.toml:
[workspace]
resolver = "2"
members = [
"dust-core",
"dust-cli",
"dust-ohos",
]
[workspace.package]
version = "1.2.4"
authors = ["bootandy <bootandy@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/bootandy/dust"
[workspace.dependencies]
rayon = "1"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"
[profile.release]
codegen-units = 1
lto = true
strip = true
2.4 核心库配置
dust-core/Cargo.toml:
[package]
name = "dust-core"
version.workspace = true
edition.workspace = true
license.workspace = true
[features]
default = []
ohos = [] # OpenHarmony 特性标志
[dependencies]
rayon.workspace = true
regex.workspace = true
serde.workspace = true
[dev-dependencies]
tempfile = "3" # 测试依赖
关键点:通过 ohos feature flag 控制 OpenHarmony 特定代码的编译。
三、平台抽象层实现
3.1 设计思路
问题:原 platform.rs 使用 #[cfg(target_family = "unix")] 和 #[cfg(target_family = "windows")],但 OpenHarmony 目标 (*-unknown-linux-ohos) 会匹配 target_family = "unix",导致无法区分普通 Unix 和 OpenHarmony。
解决方案:使用 Cargo feature flag 来控制条件编译:
// dust-core/src/platform/mod.rs
#[cfg(all(target_family = "unix", not(feature = "ohos")))]
mod unix;
#[cfg(target_family = "windows")]
mod windows;
#[cfg(feature = "ohos")]
mod ohos;
// 统一导出
#[cfg(all(target_family = "unix", not(feature = "ohos")))]
pub use unix::*;
#[cfg(target_family = "windows")]
pub use windows::*;
#[cfg(feature = "ohos")]
pub use ohos::*;
3.2 OpenHarmony 平台实现
OpenHarmony 基于 Linux 内核,可以复用大部分 Unix API:
// dust-core/src/platform/ohos.rs
use std::os::unix::fs::MetadataExt;
use std::path::Path;
/// 文件元数据
pub struct FileMetadata {
pub size: u64,
pub inode_device: Option<(u64, u64)>,
pub file_time: (i64, i64, i64), // mtime, atime, ctime
}
pub struct OhosPlatform;
impl OhosPlatform {
pub fn get_metadata<P: AsRef<Path>>(
path: P,
use_apparent_size: bool,
follow_links: bool,
) -> Option<FileMetadata> {
let metadata = if follow_links {
path.as_ref().metadata()
} else {
path.as_ref().symlink_metadata()
};
match metadata {
Ok(md) => {
let file_size = md.len();
let size = if use_apparent_size {
file_size
} else {
// 计算实际块大小
// OpenHarmony 使用标准 512 字节块
let blocks = md.blocks();
blocks * 512
};
Some(FileMetadata {
size,
inode_device: Some((md.ino(), md.dev())),
file_time: (md.mtime(), md.atime(), md.ctime()),
})
}
Err(_) => None,
}
}
}
3.3 OpenHarmony 平台注意事项
在 OpenHarmony 上开发需要注意以下限制:
| 限制类型 | 说明 | 解决方案 |
|---|---|---|
| 沙箱限制 | 应用只能访问自己的沙箱目录 | 限制扫描范围到允许的路径 |
| 权限申请 | 访问外部存储需要申请权限 | 在 module.json5 中声明权限 |
| 路径差异 | 系统路径与标准 Linux 不同 | 使用正确的鸿蒙路径 |
常见可访问路径:
/data/storage/el1/base/<bundle_name>/ - 应用私有目录
/data/storage/el2/base/<bundle_name>/ - 加密目录(需要解锁)
/data/storage/el2/base/<bundle_name>/cache/ - 缓存目录
/data/storage/el2/base/<bundle_name>/files/ - 文件目录
四、核心模块迁移
4.1 Node 模块迁移
问题:原代码中 node.rs 依赖 cli.rs 中的 FileTime 定义。
解决方案:在 dust-core 中重新定义 FileTime 枚举:
// dust-core/src/types.rs
/// 文件时间排序类型
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileTime {
Modified,
Accessed,
Changed,
}
impl Default for FileTime {
fn default() -> Self {
FileTime::Modified
}
}
CLI 模块通过 From trait 进行转换:
// dust-cli/src/cli.rs
impl From<CliFileTime> for dust_core::FileTime {
fn from(t: CliFileTime) -> Self {
match t {
CliFileTime::Modified => dust_core::FileTime::Modified,
CliFileTime::Accessed => dust_core::FileTime::Accessed,
CliFileTime::Changed => dust_core::FileTime::Changed,
}
}
}
4.2 Walker 模块迁移
问题:原 dir_walker.rs 硬编码了终端进度指示器,无法在 NAPI 环境中使用。
解决方案:
- 重命名为
walker.rs - 设计可选的进度回调接口
// dust-core/src/walker.rs
/// 进度回调 trait
pub trait ProgressCallback: Send + Sync {
fn on_progress(&self, current: usize, total: Option<usize>);
fn on_error(&self, path: &Path, error: &str);
}
/// 空实现(用于不需要进度的场景)
pub struct NoProgress;
impl ProgressCallback for NoProgress {
fn on_progress(&self, _current: usize, _total: Option<usize>) {}
fn on_error(&self, _path: &Path, _error: &str) {}
}
/// 扫描配置
pub struct WalkConfig {
pub progress: Option<Box<dyn ProgressCallback>>,
pub threads: usize,
pub follow_links: bool,
// ...
}
4.3 Scanner API 设计
为 NAPI 绑定提供简洁的 API:
// dust-core/src/scanner.rs
use crate::{WalkConfig, FileTime};
use std::path::PathBuf;
/// 扫描结果
pub struct ScanResult {
pub total_size: u64,
pub file_count: u64,
pub dir_count: u64,
pub nodes: Vec<FileNode>,
pub errors: ScanErrors,
}
/// 扫描错误信息
pub struct ScanErrors {
pub no_permissions: Vec<PathBuf>,
pub not_found: Vec<PathBuf>,
pub other: Vec<(PathBuf, String)>,
}
/// 同步扫描(阻塞)
pub fn scan_sync(paths: Vec<PathBuf>, config: ScanConfig) -> ScanResult {
// 实现扫描逻辑
}
/// 快速获取目录大小
pub fn get_directory_size(path: &Path) -> u64 {
// 快速计算,不构建完整树
}
/// 获取文件数量
pub fn get_file_count(path: &Path) -> u64 {
// 快速计数
}
五、NAPI 绑定层实现
5.1 NAPI 库配置
dust-ohos/Cargo.toml:
[package]
name = "dust-ohos"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["cdylib"] # 编译为动态库
[dependencies]
dust-core = { path = "../dust-core", features = ["ohos"] }
napi-ohos = "1"
napi-derive-ohos = "1"
serde = { workspace = true }
serde_json = { workspace = true }
[build-dependencies]
napi-build-ohos = "1"
5.2 build.rs 配置
// dust-ohos/build.rs
extern crate napi_build_ohos;
fn main() {
napi_build_ohos::setup();
}
5.3 NAPI 导出函数
// dust-ohos/src/lib.rs
use napi_derive_ohos::napi;
use dust_core::{scan_sync, ScanConfig};
use std::path::PathBuf;
mod types;
mod error;
use types::*;
/// 扫描单个目录
#[napi]
pub fn scan_directory(
path: String,
options: Option<ScanOptionsJs>
) -> napi_ohos::Result<ScanResultJs> {
let config = options
.map(ScanConfig::from)
.unwrap_or_default();
let paths = vec![PathBuf::from(path)];
let result = scan_sync(paths, config);
Ok(ScanResultJs::from(result))
}
/// 扫描多个目录
#[napi]
pub fn scan_directories(
paths: Vec<String>,
options: Option<ScanOptionsJs>
) -> napi_ohos::Result<ScanResultJs> {
let config = options
.map(ScanConfig::from)
.unwrap_or_default();
let paths: Vec<PathBuf> = paths.into_iter()
.map(PathBuf::from)
.collect();
let result = scan_sync(paths, config);
Ok(ScanResultJs::from(result))
}
/// 快速获取目录大小
#[napi]
pub fn get_directory_size(path: String) -> i64 {
dust_core::get_directory_size(&PathBuf::from(path)) as i64
}
/// 获取文件数量
#[napi]
pub fn get_file_count(path: String) -> i64 {
dust_core::get_file_count(&PathBuf::from(path)) as i64
}
/// 格式化大小显示
#[napi]
pub fn format_size(size: i64, format: Option<String>) -> String {
let fmt = format.as_deref().unwrap_or("");
dust_core::utils::human_readable_number(size as u64, fmt)
}
/// 检查路径是否可访问
#[napi]
pub fn is_path_accessible(path: String) -> bool {
std::fs::metadata(&path).is_ok()
}
/// 获取版本信息
#[napi]
pub fn get_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
5.4 TypeScript 类型定义
dust-ohos/index.d.ts:
/* auto-generated by NAPI-RS */
export interface ScanOptions {
/** 是否忽略隐藏文件 */
ignoreHidden?: boolean;
/** 最大扫描深度 */
maxDepth?: number;
/** 是否跟踪符号链接 */
followLinks?: boolean;
/** 使用的线程数 */
threads?: number;
/** 过滤正则表达式 */
filterRegex?: string[];
/** 忽略的目录名 */
ignoreDirectories?: string[];
/** 按文件数量而非大小排序 */
byFilecount?: boolean;
}
export interface FileNode {
/** 文件/目录名 */
name: string;
/** 完整路径 */
path: string;
/** 大小(字节)或文件数量 */
size: number;
/** 是否为目录 */
isDirectory: boolean;
/** 子节点 */
children: FileNode[];
}
export interface ScanErrors {
/** 无权限访问的路径 */
noPermissions: string[];
/** 未找到的路径 */
notFound: string[];
/** 其他错误 */
other: Array<{ path: string; error: string }>;
}
export interface ScanResult {
/** 总大小(字节) */
totalSize: number;
/** 文件数量 */
fileCount: number;
/** 目录数量 */
dirCount: number;
/** 文件树 */
nodes: FileNode[];
/** 错误信息 */
errors: ScanErrors;
}
/** 扫描单个目录 */
export function scanDirectory(
path: string,
options?: ScanOptions
): ScanResult;
/** 扫描多个目录 */
export function scanDirectories(
paths: string[],
options?: ScanOptions
): ScanResult;
/** 快速获取目录大小 */
export function getDirectorySize(path: string): number;
/** 获取文件数量 */
export function getFileCount(path: string): number;
/** 格式化大小显示 */
export function formatSize(size: number, format?: string): string;
/** 检查路径是否可访问 */
export function isPathAccessible(path: string): boolean;
/** 获取版本信息 */
export function getVersion(): string;
六、构建脚本编写
6.1 HNP 构建脚本
HNP (HarmonyOS Native Package) 是鸿蒙系统的原生包格式,用于分发命令行工具。
scripts/build-hnp.sh:
#!/bin/bash
# OpenHarmony HNP 构建脚本
# 用法: ./scripts/build-hnp.sh [arch]
# arch: aarch64, armv7, x86_64, all (默认)
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
echo_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 项目信息
PKG_NAME="dust"
PKG_VERSION="1.2.4"
PKG_DESCRIPTION="A more intuitive version of du - disk usage analyzer"
# 检查环境变量
check_environment() {
if [ -z "$OHOS_NDK_HOME" ]; then
echo_error "OHOS_NDK_HOME is not set"
echo "Please set OHOS_NDK_HOME to your OpenHarmony NDK path"
echo "Example: export OHOS_NDK_HOME=/path/to/ohos-sdk/native"
exit 1
fi
echo_info "OHOS_NDK_HOME: $OHOS_NDK_HOME"
if [ ! -d "$OHOS_NDK_HOME" ]; then
echo_error "OHOS_NDK_HOME path does not exist: $OHOS_NDK_HOME"
exit 1
fi
}
# 获取架构名称映射
get_arch_name() {
local target=$1
case "$target" in
aarch64-unknown-linux-ohos) echo "arm64-v8a" ;;
armv7-unknown-linux-ohos) echo "armeabi-v7a" ;;
x86_64-unknown-linux-ohos) echo "x86_64" ;;
esac
}
# 构建函数
build_target() {
local target=$1
echo_info "Building CLI for $target..."
export PATH="$OHOS_NDK_HOME/llvm/bin:$PATH"
cargo build --release --target "$target" -p du-dust
if [ $? -eq 0 ]; then
echo_info "Build successful for $target"
else
echo_error "Build failed for $target"
return 1
fi
}
# 创建 HNP 包
create_hnp() {
local target=$1
local arch=$(get_arch_name "$target")
local hnp_dir="dist/hnp/$arch"
local bin_dir="$hnp_dir/bin"
echo_info "Creating HNP package for $arch..."
mkdir -p "$bin_dir"
# 复制可执行文件
local exe_path="target/$target/release/dust"
if [ -f "$exe_path" ]; then
cp "$exe_path" "$bin_dir/"
chmod +x "$bin_dir/dust"
else
echo_error "Executable not found: $exe_path"
return 1
fi
# 创建 hnp.json 配置文件
cat > "$hnp_dir/hnp.json" << EOF
{
"name": "$PKG_NAME",
"version": "$PKG_VERSION",
"description": "$PKG_DESCRIPTION",
"type": "hnp",
"install": {
"links": [
{
"source": "bin/dust",
"target": "dust"
}
]
}
}
EOF
# 打包 HNP
local hnp_file="dist/${PKG_NAME}_${PKG_VERSION}_${arch}.hnp"
(cd "$hnp_dir" && zip -r "../../${PKG_NAME}_${PKG_VERSION}_${arch}.hnp" . -x "*.DS_Store")
echo_info "Created HNP package: $hnp_file"
}
# 主函数
main() {
local arch="${1:-all}"
echo_info "=== OpenHarmony HNP Build Script ==="
echo_info "Building $PKG_NAME v$PKG_VERSION"
check_environment
# 安装 Rust 目标
rustup target add aarch64-unknown-linux-ohos 2>/dev/null || true
rustup target add armv7-unknown-linux-ohos 2>/dev/null || true
rustup target add x86_64-unknown-linux-ohos 2>/dev/null || true
rm -rf dist/hnp
case "$arch" in
aarch64)
build_target "aarch64-unknown-linux-ohos"
create_hnp "aarch64-unknown-linux-ohos"
;;
armv7)
build_target "armv7-unknown-linux-ohos"
create_hnp "armv7-unknown-linux-ohos"
;;
x86_64)
build_target "x86_64-unknown-linux-ohos"
create_hnp "x86_64-unknown-linux-ohos"
;;
all)
for target in aarch64-unknown-linux-ohos armv7-unknown-linux-ohos x86_64-unknown-linux-ohos; do
build_target "$target"
create_hnp "$target"
done
;;
*)
echo_error "Unknown architecture: $arch"
echo "Usage: $0 [aarch64|armv7|x86_64|all]"
exit 1
;;
esac
echo_info "=== Build Complete ==="
echo ""
echo "HNP packages created in 'dist/' directory:"
ls -la dist/*.hnp 2>/dev/null || echo "No HNP files found"
}
main "$@"
6.2 NAPI 库构建脚本
scripts/build-ohos.sh:
#!/bin/bash
# OpenHarmony NAPI 库构建脚本
set -e
PKG_NAME="dust_ohos"
check_environment() {
if [ -z "$OHOS_NDK_HOME" ]; then
echo "Error: OHOS_NDK_HOME is not set"
exit 1
fi
}
build_target() {
local target=$1
local arch=$2
echo "Building NAPI library for $target..."
export PATH="$OHOS_NDK_HOME/llvm/bin:$PATH"
cargo build --release --target "$target" -p dust-ohos
mkdir -p "dist/$arch"
cp "target/$target/release/lib${PKG_NAME}.so" "dist/$arch/"
echo "Created: dist/$arch/lib${PKG_NAME}.so"
}
main() {
check_environment
rm -rf dist
build_target "aarch64-unknown-linux-ohos" "arm64-v8a"
build_target "armv7-unknown-linux-ohos" "armeabi-v7a"
build_target "x86_64-unknown-linux-ohos" "x86_64"
# 复制类型定义
cp dust-ohos/index.d.ts dist/
echo "Build complete! Output in 'dist/' directory"
}
main "$@"
6.3 执行构建
# 构建 HNP 包(命令行工具)
chmod +x scripts/build-hnp.sh
./scripts/build-hnp.sh
# 构建 NAPI 库(可选)
chmod +x scripts/build-ohos.sh
./scripts/build-ohos.sh
构建输出示例:
[INFO] === OpenHarmony HNP Build Script ===
[INFO] Building dust v1.2.4
[INFO] OHOS_NDK_HOME: /Users/xxx/Library/OpenHarmony/Sdk/12/native
[INFO] Building CLI for aarch64-unknown-linux-ohos...
Compiling dust-core v1.2.4
Compiling du-dust v1.2.4
Finished release [optimized] target(s) in 45.23s
[INFO] Build successful for aarch64-unknown-linux-ohos
[INFO] Creating HNP package for arm64-v8a...
[INFO] Created HNP package: dist/dust_1.2.4_arm64-v8a.hnp
[INFO] === Build Complete ===
HNP packages created in 'dist/' directory:
-rw-r--r-- 1 user staff 1.2M Jan 26 10:30 dust_1.2.4_arm64-v8a.hnp
七、部署与测试
7.1 连接设备
# 查看已连接设备
hdc list targets
# 输出示例
# 1234567890ABCDEF device
7.2 安装 HNP 包
# 发送 HNP 包到设备
hdc file send dist/dust_1.2.4_arm64-v8a.hnp /data/local/tmp/
# 安装 HNP 包
hdc shell hnp install /data/local/tmp/dust_1.2.4_arm64-v8a.hnp
7.3 运行测试
# 进入设备 shell
hdc shell
# 运行 dust
/data/app/bin/dust /data
# 示例输出
# 1.2G ┌── storage
# 800M │ ├── el2
# 400M │ │ └── base
# 400M │ └── el1
# ...
7.4 CLI 命令参考
# 基本用法
dust [目录路径]
# 常用选项
dust -d 3 /data # 限制深度为 3
dust -r /data # 反向排序
dust -n 20 /data # 显示前 20 个
dust -H /data # 忽略隐藏文件
dust -c /data # 按文件数量排序
dust -j /data # JSON 输出
dust -s /data # 使用 SI 单位(KB, MB)
dust -b /data # 使用二进制单位(KiB, MiB)
八、ArkTS 使用示例(NAPI 方式)
如果选择使用 NAPI 绑定,以下是在 ArkTS 中的使用示例:
// 导入 NAPI 库
import {
scanDirectory,
scanDirectories,
getDirectorySize,
formatSize,
getVersion,
getFileCount,
isPathAccessible,
ScanOptions,
ScanResult,
FileNode
} from 'libdust_ohos.so';
// 示例 1: 扫描单个目录
function scanSingleDirectory(): void {
const path = '/data/storage/el2/base';
if (isPathAccessible(path)) {
const result: ScanResult = scanDirectory(path, {
ignoreHidden: true,
maxDepth: 5
});
console.log(`Total size: ${formatSize(result.totalSize)}`);
console.log(`File count: ${result.fileCount}`);
// 打印错误信息
if (result.errors.noPermissions.length > 0) {
console.log(`Permission denied for: ${result.errors.noPermissions.join(', ')}`);
}
}
}
// 示例 2: 扫描多个目录
function scanMultipleDirectories(): void {
const paths = [
'/data/storage/el1/base',
'/data/storage/el2/base'
];
const result = scanDirectories(paths, {
followLinks: false,
threads: 4
});
console.log(`Combined size: ${formatSize(result.totalSize)}`);
}
// 示例 3: 快速获取目录大小
function getQuickSize(): void {
const cacheDir = '/data/storage/el2/base/cache';
const size = getDirectorySize(cacheDir);
console.log(`Cache directory size: ${formatSize(size)}`);
}
// 示例 4: 遍历文件树
function traverseFileTree(node: FileNode, indent: string = ''): void {
const sizeStr = formatSize(node.size);
console.log(`${indent}${node.name} (${sizeStr})`);
for (const child of node.children) {
traverseFileTree(child, indent + ' ');
}
}
// 示例 5: 查找大文件(>10MB)
function findLargeFiles(nodes: FileNode[], minSize: number = 10 * 1024 * 1024): FileNode[] {
const largeFiles: FileNode[] = [];
function traverse(node: FileNode): void {
if (!node.isDirectory && node.size >= minSize) {
largeFiles.push(node);
}
for (const child of node.children) {
traverse(child);
}
}
for (const node of nodes) {
traverse(node);
}
return largeFiles.sort((a, b) => b.size - a.size);
}
// 示例 6: 使用过滤器
function scanWithFilters(): void {
const result = scanDirectory('/data/storage/el2/base', {
// 只扫描 .log 文件
filterRegex: ['\\.log$'],
// 忽略 node_modules 目录
ignoreDirectories: ['node_modules', '.git'],
// 忽略隐藏文件
ignoreHidden: true
});
console.log(`Log files total: ${formatSize(result.totalSize)}`);
}
九、遇到的问题与解决方案
问题 1:找不到链接器
错误信息:
error: linker `aarch64-unknown-linux-ohos-clang` not found
原因:OHOS NDK 的 llvm/bin 目录未添加到 PATH。
解决方案:
# 确保 NDK 路径正确
echo $OHOS_NDK_HOME
ls $OHOS_NDK_HOME/llvm/bin/
# 将 llvm/bin 添加到 PATH
export PATH="$OHOS_NDK_HOME/llvm/bin:$PATH"
问题 2:平台条件编译冲突
问题描述:OpenHarmony 目标 (*-unknown-linux-ohos) 会匹配 target_family = "unix",导致无法使用标准的条件编译区分平台。
解决方案:使用 Cargo feature flag 区分:
# Cargo.toml
[features]
ohos = []
// 条件编译
#[cfg(all(target_family = "unix", not(feature = "ohos")))]
mod unix;
#[cfg(feature = "ohos")]
mod ohos;
# 构建时启用 feature
cargo build --features ohos --target aarch64-unknown-linux-ohos
问题 3:CLI 与核心库耦合
问题描述:原代码中 node.rs 依赖 cli.rs 中的 FileTime 定义,无法分离为独立的核心库。
解决方案:在 dust-core 中重新定义 FileTime 枚举,CLI 模块通过 From trait 转换。
问题 4:进度指示器依赖
问题描述:原 dir_walker.rs 硬编码了终端进度指示器(使用 indicatif crate),无法在 NAPI 环境中使用。
解决方案:设计可选的进度回调接口:
pub trait ProgressCallback: Send + Sync {
fn on_progress(&self, current: usize, total: Option<usize>);
}
CLI 和 NAPI 可以提供不同的实现,或使用空实现。
问题 5:tempfile 测试依赖缺失
问题描述:scanner.rs 中的测试使用了 tempfile crate,但未在 Cargo.toml 中声明。
错误信息:
error[E0432]: unresolved import `tempfile`
解决方案:在 dust-core/Cargo.toml 中添加开发依赖:
[dev-dependencies]
tempfile = "3"
问题 6:未使用的错误处理函数警告
问题描述:error.rs 中定义的辅助函数(如 to_napi_error、permission_error)未被使用,产生编译警告。
解决方案:添加属性允许死代码(这些函数是为将来的错误处理预留的):
#![allow(dead_code)]
问题 7:build.rs 路径问题
问题描述:原始 build.rs 使用相对路径生成 completions 和 man-page,在工作空间结构下路径不正确,导致文件生成到错误的位置。
解决方案:使用 CARGO_MANIFEST_DIR 环境变量获取正确的 crate 根目录路径:
// build.rs
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string());
let completions_dir = Path::new(&manifest_dir).join("completions");
let man_dir = Path::new(&manifest_dir).join("man-page");
问题 8:权限不足
问题描述:在设备上运行时无法访问某些目录。
解决方案:
- 只扫描应用沙箱内的目录
- 在
module.json5中申请必要权限:
{
"requestPermissions": [
{
"name": "ohos.permission.READ_MEDIA"
}
]
}
问题 9:找不到共享库
错误信息:
error while loading shared libraries: libxxx.so: cannot open shared object file
解决方案:确保使用静态链接,在 Cargo.toml 中配置:
[profile.release]
lto = true # 链接时优化,有助于减少外部依赖
十、总结
本文以 dust 项目为例,完整演示了 Rust CLI 工具的鸿蒙化适配流程:
| 阶段 | 关键步骤 | 主要产出 |
|---|---|---|
| 环境配置 | 安装 Rust 目标、配置 SDK 路径 | .cargo/config.toml |
| 项目重构 | 工作空间结构、模块分离 | dust-core, dust-cli, dust-ohos |
| 平台适配 | 平台抽象层、feature flag | platform/ohos.rs |
| 构建打包 | HNP 构建脚本、交叉编译 | .hnp 包 |
| 部署测试 | HDC 工具安装、功能验证 | 设备上运行成功 |
核心要点
- 使用 feature flag 区分 Unix 和 OpenHarmony:因为
*-unknown-linux-ohos目标会匹配target_family = "unix" - OpenHarmony 基于 Linux,可复用 Unix API:
std::os::unix::fs::MetadataExt等接口可以直接使用 - 注意沙箱限制和权限管理:应用只能访问自己的沙箱目录,访问外部存储需要申请权限
- HNP 是原生命令行工具的标准打包格式:包含
hnp.json配置文件和可执行文件 - NAPI 绑定可选:如果只需要命令行工具,可以跳过 NAPI 绑定部分
构建方式选择
| 方式 | 用途 | 输出 | 构建脚本 |
|---|---|---|---|
| HNP 原生命令行 | 在鸿蒙终端直接使用 | .hnp 包 |
build-hnp.sh |
| NAPI 绑定 | 供 ArkTS/JS 调用 | .so 库 |
build-ohos.sh |
参考资源
- ohos-rs 官方文档
- ohos-rs GitHub
- OpenHarmony 开发者文档
- Rust OpenHarmony 平台支持
- HDC 工具使用指南
- napi-ohos crate
- napi-build-ohos crate
本文基于 dust v1.2.4 的实际适配经验编写。适配过程中共遇到并解决了 9 个主要问题,从项目结构重构到平台抽象层设计,再到构建脚本编写,完整记录了整个鸿蒙化适配流程。
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
更多推荐

所有评论(0)