HarmonyOS鸿蒙PC平台nodejs三方库移植实战(bcrypt和 sqlite3移植笔记)

本文记录下移植nodejs三方库bcrypt和 sqlite3到鸿蒙PC上的踩坑过程和经验分享。鸿蒙PC平台nodejs三方库移植也很简单。鸿蒙 PC 安装 bcrypt 和sqlite3三方库实录:预编译与源码编译双路实战,关于 HarmonyOS + Node.js 原生模块的兼容性测试验证总结笔记。


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

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

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

背景

在鸿蒙 PC 上,(安装好node环境的前提下)执行了这样一个简单的命令:

npm install bcrypt

然后——没有任何报错node-gyp 没有崩毁,编译器没有报缺头文件,3 个包安静地装好,测试用例全部通过。

这在 Node.js 原生模块的安装史上,算得上一个"小反常"。

不过后面我们进一步验证了:不仅预编译能装,强制走源码编译也完全 OK。鸿蒙 PC 上的 C++ 工具链(来自 Harmonybrew 的 clang、make、python3)完整可用。

注: 踩坑记,从应用市场上下载安装的node-H软件包,版本23.x有问题啊,直接崩掉,建议卸载掉它。推荐使用 Harmonybrew 的软件包工具安装node环境。


安装成功真机截图:
在这里插入图片描述

bcrypt 是什么?

bcrypt 是一个密码哈希库,以 C++ 编写,通过 Node-API 暴露给 JavaScript。它的安装有两种路径:

路径 A(预编译):
bcrypt 官方发布的 .node 二进制
     → node-gyp-build(安装时脚本)
复制到 node_modules 中

路径 B(源码编译):
bcrypt 源码 (.cc/.h)
     → node-gyp 调用 C++ 编译器
生成 .node 二进制

路径 A 静默无感,路径 B 则依赖完整的 C/C++ 编译工具链。绝大多数安装报错都发生在路径 B。

常规安装常见的坑

如果走源码编译路径,鸿蒙系统并不默认提供 GNU/Unix 开发工具链,典型的报错有:

错误信息根因
gyp ERR! stack Error: not found: make缺 GNU Make
fatal error: 'node.h' file not foundNode 头文件未安装
fatal error: 'openssl/opensslv.h' file not foundOpenSSL 开发头文件缺失
gyp ERR! stack Error: Could not find any Python installationPython 未安装(node-gyp 依赖)
clang: error: no such file or directory编译器路径未配置

环境准备:在鸿蒙 PC 上搭建 C++ 编译工具链

在尝试安装原生模块之前,需要先确保鸿蒙 PC 具备基本的 C/C++ 编译环境。这块依赖 Harmonybrew(专为 OpenHarmony 移植的 Homebrew 发行版)来完成。

⛔️ HMDFS 文件系统缺陷
HiShell 环境中 TMPDIR 默认指向 /storage/Users/currentUser,该目录承载在 HMDFS 上。HMDFS 存在已知文件系统缺陷,可能导致 node-gyp 编译过程中临时文件写入失败。

建议在安装工具链后,先设置环境变量将临时目录和工作目录指向非 HMDFS 路径:

export TMPDIR=/data/storage/el2/base/files/tmp
mkdir -p $TMPDIR

详见 featured-packages.md 场景一 中的完整说明。

安装 Harmonybrew

步骤操作
① 卸载冲突软件如果安装了 GitNext 和 DevBox,先卸载
② 打开安全开关设置 → 系统 → 开发者选项 → 打开"开发者选项";设置 → 隐私和安全 → 高级 → 打开"运行来自非应用市场的扩展程序"
③ 开启隐藏开发者菜单设置 → 关于本机 → 连续点击"软件版本"7 次 → 确认重启
④ 安装 Harmonybrewzsh -c "$(curl -fsSL https://harmonybrew.atomgit.com/install.sh)"
⑤ 配置环境变量echo 'eval "$(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)"' >> ~/.zshrc 然后执行 eval 使生效

本文收录于 Harmonybrew 特色软件包清单

安装编译所需工具

推荐使用 Harmonybrew 的一站式开发工具包:

# 安装开发工具包(含 ohos-sdk clang、llvm-gcc-compat、make、coreutils 等)
brew install devel-base

# 同时安装 Python(node-gyp 依赖)
brew install python

💡 为什么推荐 devel-base 它相当于 Debian 中的 build-essential,级联依赖了 ohos-sdkllvm-gcc-compatmakecoreutils 等包。其中 ohos-sdk 提供的 clang 链接器经过了封装,编译产物会自动带有代码签名,确保 .node 文件能被 Node.js 运行时正常加载。

如果只需要验证编译链路通不通,安装普通 LLVM 也可以:

brew install llvm make cmake python

但正式项目建议使用 devel-base,避免因代码签名问题导致运行时加载失败。

安装后检查关键工具是否就绪:

clang++ --version   # C++ 编译器
make --version      # GNU Make
python3 --version   # node-gyp 脚本依赖

💡 提示brew list 可查看已安装的全部包;brew search [keyword] 搜索可用包;brew update 更新包索引。

这套环境就绪后,鸿蒙 PC 就具备了编译绝大多数 node-gyp 原生模块的能力。下面安装 bcrypt 就不再依赖预编译路径——即使用走源码编译,工具链也完整可用。


为什么这次这么顺利?

1. bcrypt@6.0.0 用了预编译策略,且覆盖了鸿蒙的架构

这是最核心的原因

bcrypt 从 6.x 开始,安装脚本从 node-gyp rebuild(每次都编译)切换为 node-gyp-build(优先下载预编译二进制)。而 bcrypt@6.0.0 的官方预编译包覆盖了以下平台:

prebuilds/
├── darwin-arm64
├── darwin-x64
├── linux-arm
│   ├── bcrypt.glibc.node
│   └── bcrypt.musl.node
├── linux-arm64
│   ├── bcrypt.glibc.node       → glibc 变体
│   └── bcrypt.musl.node         → musl 变体,鸿蒙命中
├── linux-x64
│   ├── bcrypt.glibc.node
│   └── bcrypt.musl.node
├── win32-arm64
├── win32-x64

安装时 node-gyp-build 检测到 linux-arm64 + musl libc,精准匹配了 bcrypt.musl.node,直接复制使用,完全绕过了 C++ 编译环节

2. --tag-libc 构建参数正确生成了 musl 变体

bcrypt@6.0.0 的 npm 构建脚本是:

prebuildify --napi --tag-libc --strip

--tag-libc 参数让构建 CI 分别为 glibc 和 musl 各生成一个 .node 变体:

linux-arm64/
├── bcrypt.glibc.node      → 链接 glibc,适用 Ubuntu/Debian 等
└── bcrypt.musl.node        → 链接 musl,适用 Alpine/HarmonyOS

鸿蒙 PC 的系统 libc 是 musl,node-gyp-build 自动选择了 bcrypt.musl.node。不依赖任何二进制兼容层,完美匹配。

3. 即使强制走源码编译,Harmonybrew 工具链也完全胜任

我们还做了另一个实验——删除 build/ 目录后强制走源码编译:

rm -rf node_modules/bcrypt/build
npm rebuild bcrypt --foreground-scripts

输出清晰显示:clang++ 被调用编译了 src/blowfish.ccsrc/bcrypt.ccsrc/bcrypt_node.cc,链接生成了 bcrypt_lib.node。测试全部通过。

在这里插入图片描述

这意味着 Harmonybrew 安装的 clang 编译器、GNU Make、Python 3、全链路可用

依赖状态
clang++(C++ 编译器)✅ 来自 Harmonybrew
make(构建工具)✅ 来自 Harmonybrew
python3(node-gyp 脚本依赖)✅ 来自 Harmonybrew
node.h(Node API 头文件)✅ 随 Node 安装自带

4. 完整的测试用例验证了功能正确性

为了确认编译产物正确可用,我们编写了一组覆盖 bcrypt 核心 API 的测试用例,涵盖:

  • 异步 hash + compare:多个密码串(含中文、空密码)
  • 不同 saltRounds:验证 $2b$ 格式和轮数编码
  • 同步 APIhashSync / compareSync
  • 边界情况:空密码输入
  • getRounds:校验 hash 中编码的 saltRounds 值
const bcrypt = require('bcrypt');

const passwords = ['test123', 'Hello@2024', 'aB3#xyZ!', '密码123', ''];
const saltRounds = [4, 10, 12];

async function runTests() {
  let passed = 0;
  let failed = 0;

  console.log('🧪 bcrypt 测试用例\n' + '='.repeat(40));

  // 测试 1: 基础 hash + compare
  console.log('\n📌 测试 1: hash + compare 基本流程');
  for (const pw of passwords) {
    try {
      const hash = await bcrypt.hash(pw, 10);
      const match = await bcrypt.compare(pw, hash);
      const wrong = await bcrypt.compare('wrong-' + pw, hash);
      const ok = match === true && wrong === false;
      console.log(`   ${ok ? '✅' : '❌'} hash("${pw || '(空)'}") → compare → ${ok ? '正确' : '失败'}`);
      ok ? passed++ : failed++;
    } catch (e) {
      console.log(`   ❌ hash("${pw || '(空)'}") 抛出异常: ${e.message}`);
      failed++;
    }
  }

  // 测试 2: 不同 saltRounds
  console.log('\n📌 测试 2: 不同 saltRounds');
  for (const r of saltRounds) {
    try {
      const hash = await bcrypt.hash('test123', r);
      const parts = hash.split('$');
      const rounds = parts[2];
      const salt = (parts[3] || '').slice(0, 6);
      const ok = rounds === String(r) && parts[1] === '2b';
      console.log(`   ${ok ? '✅' : '❌'} saltRounds=${r} → $${parts[1]}$${rounds}$${salt}...`);
      ok ? passed++ : failed++;
    } catch (e) {
      console.log(`   ❌ saltRounds=${r} 异常: ${e.message}`);
      failed++;
    }
  }

  // 测试 3: 同步 API
  console.log('\n📌 测试 3: 同步 API (hashSync / compareSync)');
  try {
    const hash = bcrypt.hashSync('sync-test', 8);
    const ok = bcrypt.compareSync('sync-test', hash) && !bcrypt.compareSync('wrong', hash);
    console.log(`   ${ok ? '✅' : '❌'} hashSync + compareSync ${ok ? '正常' : '异常'}`);
    ok ? passed++ : failed++;
  } catch (e) {
    console.log(`   ❌ sync API 异常: ${e.message}`);
    failed++;
  }

  // 测试 4: 空值保护
  console.log('\n📌 测试 4: 边界情况');
  try {
    const h1 = bcrypt.hashSync('', 4);
    const ok = bcrypt.compareSync('', h1);
    console.log(`   ${ok ? '✅' : '❌'} 空密码 hash+compare ${ok ? '正常' : '异常'}`);
    ok ? passed++ : failed++;
  } catch (e) {
    console.log(`   ❌ 空密码处理: ${e.message}`);
    failed++;
  }

  // 测试 5: getRounds
  console.log('\n📌 测试 5: getRounds 解析 hash 轮数');
  try {
    const hash = bcrypt.hashSync('rounds-test', 11);
    const rounds = bcrypt.getRounds(hash);
    const ok = rounds === 11;
    console.log(`   ${ok ? '✅' : '❌'} getRounds() → ${rounds} ${ok ? '(正确)' : '(应为11)'}`);
    ok ? passed++ : failed++;
  } catch (e) {
    console.log(`   ❌ getRounds 异常: ${e.message}`);
    failed++;
  }

  // 汇总
  console.log('\n' + '='.repeat(40));
  const total = passed + failed;
  console.log(`📊 总计: ${total} 用例 | ✅ 通过: ${passed} | ❌ 失败: ${failed}`);
  console.log(total === passed ? '\n🎉 全部通过!' : `\n⚠️  有 ${failed} 个失败`);
}

runTests().catch(e => console.error('测试框架异常:', e));

输出结果(从源码编译的 bcrypt_lib.node 上运行):
在这里插入图片描述

🧪 bcrypt 测试用例
========================================

📌 测试 1: hash + compare 基本流程
    ✅ hash("test123") → compare → 正确
    ✅ hash("Hello@2024") → compare → 正确
    ✅ hash("aB3#xyZ!") → compare → 正确
    ✅ hash("密码123") → compare → 正确
    ✅ hash("(空)") → compare → 正确

📌 测试 2: 不同 saltRounds
    ✅ saltRounds=4 → $2b$4$zQX5l...
    ✅ saltRounds=10 → $2b$10$WrMpR...
    ✅ saltRounds=12 → $2b$12$VgCfD...

📌 测试 3: 同步 API (hashSync / compareSync)
    ✅ hashSync + compareSync 正常

📌 测试 4: 边界情况
    ✅ 空密码 hash+compare 正常

📌 测试 5: getRounds 解析 hash 轮数
    ✅ getRounds() → 11 (正确)

========================================
📊 总计: 10 用例 | ✅ 通过: 10 | ❌ 失败: 0

🎉 全部通过!

5 组测试覆盖了 10 个子用例,全部通过。无论是预编译 .node 还是源码编译的 bcrypt_lib.node,功能表现完全一致。

一个有意思的细节

新版 npm(10.x+)中,--build-from-source 已被废弃:

npm warn Unknown command: "--build-from-source"

正确的做法是:

# 方式一:删预编译二进制触发回退
rm -rf node_modules/bcrypt/prebuilds
npm rebuild bcrypt

# 方式二:直接调用 node-gyp
npm rebuild bcrypt --foreground-scripts

# 方式三:最底层
cd node_modules/bcrypt && npx node-gyp rebuild --verbose

--foreground-scripts 可以把编译过程的每一条 clang++ 命令都输出到终端,方便调试。


延伸实战:sqlite3 的移植教训

除了 bcrypt,我们还在同一台机器上移植了另一个 native 模块——sqlite3@6.0.1。这次遇到的情况完全不同,正好验证了工具链的健壮性。

首次安装:编译失败

npm install sqlite3

报错关键信息:

514 error make: ar: No such file or directory
514 error make: *** [../node-addon-api/nothing.target.mk:130: Release/obj.target/../node-addon-api/nothing.a] Error 127

在这里插入图片描述

根因分析

ar(GNU Archiver)是 binutils 的一部分,负责将 .o 目标文件打包为 .a 静态库。node-gyp 在编译 native 模块时,通过 Makefile 调用 ar 来创建中间静态库,但 Harmonybrew 默认的 devel-base 没有包含 ar

为什么 bcrypt 没遇到这个问题?

模块安装路径结果
bcryptprebuild 预编译 → 直接下载 .node 二进制✅ 成功,零编译
sqlite3prebuild-install 超时 → 回退 node-gyp rebuild 源码编译❌ 缺少 ar 失败

bcrypt 的预编译路径完全绕过了 C++ 编译环节;而 sqlite3 在预编译下载超时后回退到源码编译,才暴露出 ar 缺失的问题。

修复

安装提供 ar 的工具包:

brew install binutils

或更全面的:

brew install llvm-gcc-compat binutils

验证:

which ar      # → /harmonybrew/bin/ar
ar --version  # → GNU ar (GNU Binutils) ...

重新安装

npm rebuild sqlite3 --foreground-scripts

这次编译链路完整走通:clang++ 编译 .ccar 打包 .aclang++ 链接生成 .node,最终输出 added 57 packages,无任何 gyp ERR!
在这里插入图片描述

测试验证

参照 bcrypt 的测试风格,为 sqlite3 编写了 10 组测试用例:

#测试项覆盖内容
1打开/关闭数据库文件创建、连接生命周期
2CREATE TABLE + INSERTDDL、参数化插入、lastID
3SELECT 查询db.all()、排序、行数验证
4参数化查询防 SQL 注入、db.get()
5UPDATE修改数据、this.changes
6DELETE删除前后行数对比
7事务BEGIN/COMMIT/ROLLBACK 可见性
8错误处理NOT NULL 约束违反捕获
9serialize 串行化执行顺序保证
10内存数据库:memory: 模式读写

全部 10 组通过:
在这里插入图片描述

🧪 sqlite3 测试用例
========================================
📌 测试 1: 打开和关闭数据库    ✅
📌 测试 2: 建表 + 插入数据     ✅
📌 测试 3: SELECT 查询         ✅
📌 测试 4: 参数化查询           ✅
📌 测试 5: UPDATE              ✅
📌 测试 6: DELETE              ✅
📌 测试 7: 事务                ✅
📌 测试 8: 错误处理            ✅
📌 测试 9: serialize 串行化    ✅
📌 测试 10: 内存数据库         ✅
========================================
📊 总计: 10 用例 | ✅ 通过: 10 | ❌ 失败: 0
🎉 全部通过!

sqlite3 移植的核心经验

  1. 预编译不是万能的——bcrypt 走预编译一路顺风,但 sqlite3 的预编译下载超时后就暴露了工具链缺口
  2. ar 是编译链路的关键一环——devel-base 包含了 clang++makepython3,但缺少 binutilsarld 等)。安装 binutils 即可补齐
  3. 源码编译路径验证了工具链的完整性——修复后 sqlite3 完整走通了 C++ 编译→静态库打包→链接的全流程,证明 Harmonybrew 工具链完全胜任 node-gyp native 模块的编译需求
  4. --foreground-scripts 是调试利器——能看到完整的 clang++ 编译命令和 ar 打包过程,快速定位缺失的工具

最终结论

因素角色
bcrypt@6.x 预编译策略关键因素——走预编译路径时零依赖安装
--tag-libc 构建参数生成了 -glibc/-musl 双变体,鸿蒙精确命中后者
鸿蒙系统是 ARM64 + musl与预编译包完美匹配,无需兼容层
devel-base(Harmonybrew 工具链)预编译路径未使用,但源码编译路径完整可用

所以这背后是两个好消息:

预编译路径:bcrypt@6.0.0 的构建者用 --tag-libc 为不同 libc 分别构建了变体,鸿蒙 PC 是 linux-arm64 + musl,node-gyp-build 精准命中了 bcrypt.musl.node——零编译,装完即用。

源码编译备胎:即使预编译二进制不存在(或被人删了),devel-base 工具链已就绪,直接 npm rebuild 能顺利从 .cc 源码编译出 .node 二进制。两条路都通。

对鸿蒙上 Node.js 开发者的参考

  • 优先选择支持 prebuild 的包,安装过程最省心
  • 即使包没有预编译,有 devel-base 工具链(clang + make + python3),大部分 node-gyp 模块也能从源码编译
  • bcrypt --tag-libc 是一个好实践——如果开发 native 模块,建议也加上这个参数,对齐不同 libc 环境下安装者的体验
  • 如果遇到 --build-from-source 报 unknown command,换成 --foreground-scripts,这是新 npm 的正确方式
  • 迁移 sqlite3 等模块的额外教训:安装 binutils 补全 ar 等归档工具,确保源码编译链路完整

📸 参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值