把 JupyterLab 搬到鸿蒙 PC:Electron 壳 + 浏览器内 Python 内核的移植实战
把 JupyterLab 搬到鸿蒙 PC:Electron 壳 + 浏览器内 Python 内核的移植实战
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_jupyter

一、为什么是 JupyterLab,为什么这么难
JupyterLab 是 Jupyter 生态的下一代交互式开发环境——notebook、代码控制台、文件浏览器、多文档工作区(VS Code 式布局)一体化。数据科学、教学、脚本实验场景里它几乎是标配。
但把它搬到鸿蒙 PC 上,难点比一般的 Electron 应用多一层:
- 它是 Electron 应用——鸿蒙 PC 没有 Node.js 运行时,也没有 Chromium 壳,需要 OpenHarmony Electron 移植层(
libelectron.so) - 它依赖一个 Jupyter Server——正常部署是
python -m jupyterlab起本地服务,前端连 WebSocket。鸿蒙 PC 上跑完整 CPython Jupyter Server 意味着要交叉编译整个 Python 科学栈 - 它和 Jupyter Notebook 是「双胞胎交付」——Notebook 7 的前端本来就基于 JupyterLab 组件构建,两个产品 90% 代码同源,但任务清单要求分列交付、独立安装、独立评分
这三条决定了移植策略:Electron 壳承载 + 服务端轻量化 + 产品身份 fork。


二、总体架构:三层拆解

2.1 三种服务模式
| 模式 | 环境变量 | 说明 |
|---|---|---|
| node-static(OHOS 默认) | JUPYTERLAB_OHOS_SERVER_MODE=node-static | 主进程用 node-static 托管 Lab 前端静态资源,Python 内核跑在浏览器里 |
| python | JUPYTERLAB_OHOS_FORCE_PYTHON=1 | 设备上启动 python -m jupyterlab(需要打包 aarch64 CPython) |
| external | JUPYTERLAB_EXTERNAL_SERVER_URL=http://… | 连接局域网内已有 Jupyter Server |
为什么默认 node-static? 这是整个适配里最重要的一个决策:
- 打包完整 CPython + Jupyter Server + 依赖树,HAP 体积会失控,而且 numpy/scipy 这类 C 扩展在 OHOS aarch64 上没有现成 wheel,交叉编译是无底洞
- node-static 模式下,Lab 前端以纯静态资源运行,Python 内核换成浏览器内解释器——真机截图里状态栏显示的
Python 3 (Skulpt)就是它 - 「打开 / 编辑 / 保存 notebook + 执行单元格」这个核心闭环全部走通,体积只有纯前端的量级
代价很明确:Skulpt 是纯 JS 实现的 Python 子集解释器,print、import time/os、函数定义、循环、字符串格式化都没问题(下文真机验证),但 numpy/pandas 这类 C 扩展生态不存在。这是「能用的轻量版」和「跑不起来的完整版」之间的取舍——先让核心闭环跑起来。
2.2 和 Jupyter Notebook 工程的关系
任务清单里 JUPYTER 和 Jupyter Notebook 是两个独立交付物。技术上 Notebook 7 就是 Lab 内核换壳,所以我从已验证的 ohos_JupyterNotebook 工程 fork 出本仓库,用脚本完成「产品身份 Lab 化」:
node scripts/bootstrap-from-notebook.mjs # 仅 runtime
node scripts/bootstrap-from-notebook.mjs --with-hap # 完整 HAP 树
这个脚本做的重写包括:
- bundle 名:
org.jupyter.notebook.ohos→org.jupyter.lab.ohos - UI 字符串:Notebook → JupyterLab
- 环境变量优先级:
envFirst('NOTEBOOK_…', 'JUPYTERLAB_…')→ Lab 优先 - HNP 包类型强制
private(原因见第四节坑三)
好处是约 2000 行 Electron 主进程代码(jupyterlab.js / staticLabServer.js)不用维护两份,坏处是所有「身份相关」的东西都要仔细核对——包括签名、HNP、应用名,每一个都变成了坑。

三、构建链路
pkg/ohos/runtime (Electron 主进程源码)
│ node pkg/ohos/build-package.mjs
▼
ohos_hap/web_engine/src/main/resources/resfile/resources/app
│ node pkg/ohos/build-hap.mjs (内部调 hvigor assembleHap)
▼
electron-default-signed.hap → hdc install
一个极易搞错的细节:Electron 应用的资源树必须放进 web_engine 模块的 resfile:
ohos_hap/web_engine/src/main/resources/resfile/resources/app ← 正确
ohos_hap/electron/src/main/resources/resfile/... ← 不会被打包!
entry 模块(electron/)只是壳,真正的 Electron runtime 和应用资源都在 web_engine HAR 里。路径放错的话构建不报错,装上就是白屏。
四、四个真机坑,每一个都值得记录
这部分是整篇文章最有价值的部分。四个坑全部来自真实构建日志,按踩中顺序排列。
坑一:CompileArkTS 10705000(import lazy)
ERROR: ArkTS Compiler Error
10705000 ... 'import lazy { ... }'
现象:web_engine 模块大量使用 import lazy 语法,编译直接拒绝。
排查:import lazy 需要 API 12 beta3+ 的工具链开关。而 build-profile.json5 里 DevEco 同步时写入了:
"compatibleSdkVersion": "6.1.0(23)",
"compatibleSdkVersionStage": "beta1"
诡异的地方在于:本机装的明明是 Release SDK,但这个 beta1 stage 组合会让 es2abc(ArkTS 编译器)直接拒绝 lazy import 语法。
修复:把 product 钉在已验证可编译的组合上,且不写 stage:
"compatibleSdkVersion": "6.0.1(21)",
"targetSdkVersion": "6.0.1(21)"
// 不要 compatibleSdkVersionStage
后患:DevEco 的 Project Structure / Sync / 签名 Fix 操作都可能把这一块悄悄改回去。每次在 DevEco 里动过配置,重新构建前都要 grep 一遍这个字段。
坑二:SignHap 00303074(profile 绑定 bundle)
SignHap failed: 00303074
现象:签名阶段失败。
根因:鸿蒙的自动签名 debug profile 是绑定 bundleName 的。fork 工程时 build-profile.json5 里残留的签名材料是 Notebook 的 .p7b——它只包含 org.jupyter.notebook.ohos,拿去签 org.jupyter.lab.ohos 直接被拒。
修复:
- 清空工程里的
signingConfigs(bootstrap 脚本会自动做) - DevEco → Project Structure → Signing Configs → 自动签名 / Fix,为 Lab 自己的 bundle 重新生成
.p7b
教训:fork 任何鸿蒙工程,签名材料都要视为「每 bundle 一份」,绝不复用。
坑三:Install 9568407(HNP 原生包冲突)
Install Failed: code:9568407
Failed to install the HAP because installing the native package failed.
hilog 里的关键线索:
ProcessBundleInstallNative … hnp install: electron
[HNP API] native package install! … package name=org.jupyter.lab.ohos
already exist cfg ignore
… MSG_ERR_NATIVE_INSTALL_FAILED
现象:Notebook 已安装的情况下,Lab 装不上;卸了 Notebook,Lab 就能装。
根因:两个产品都声明了同名 HNP 原生包 jupyterlab_python.hnp(打包 Python 运行时用的)。Notebook 声明的类型是 "public"(设备全局唯一),Lab 再声明同名 public 包,系统检测到「已存在同名配置」直接失败。
修复:Lab 侧把 HNP 声明改为应用私有:
// ohos_hap/electron/src/main/module.json5
"hnpPackages": [
{ "package": "jupyterlab_python.hnp", "type": "private" }
]
private 类型的 HNP 作用域是单个应用,Lab 和 Notebook 各自持有一份,互不干扰,两个产品从此可以共存。这个改动要重新 assembleHap,因为模块元数据是烘焙进 HAP 的。
坑四:Install 9568320(no signature file)
Install Failed: error: failed to install bundle.
code:9568320
error: no signature file.
现象:明明刚在 DevEco 里做完了自动签名(~/.ohos/config/ 里的材料时间戳就是刚才),构建也成功了,装机还是说没有签名。
排查:看产物文件名——electron-default-unsigned.hap。unsigned 三个字就在文件名里。
根因:DevEco 的自动签名只负责两件事——生成材料、写入 build-profile.json5 的 signingConfigs 块。但 products[].signingConfig 这个引用字段它不管:
"signingConfigs": [
{ "name": "default", "material": { ... } } // ← 材料齐全
],
"products": [
{ "name": "default",
"signingConfig": "", // ← 留空!签名任务被跳过
signingConfig: "" 时 hvigor 会静默跳过 SignHap,产出 unsigned HAP,构建全程无警告,直到 bm install 才报 9568320。
修复:
"signingConfig": "default", // 指向 signingConfigs 里的配置名
重新构建后 SignHap 任务执行 12 秒,产物变成 electron-default-signed.hap,装机一次通过。
这个坑我在同一天踩了三次(Terminator、JupyterLab、JupyterNotebook 三个工程),可以确认它是 DevEco 的系统性问题:自动签名永远不回填 products 引用。判断方法就一条:构建完看产物文件名,带 unsigned 就是没签上。
五、真机验收:七个功能点逐一过
设备:HUAWEI MateBook Pro,HarmonyOS 7.0.0。安装产物 electron-default-signed.hap 约 528MB(含 Electron runtime + Lab 前端 + HNP Python 包)。
5.1 Launcher 启动器
启动后首先进入 Launcher——这是 JupyterLab 区别于 Notebook 的标志性界面,新建入口集中在一屏:

左侧文件浏览器、右侧 Launcher 卡片布局完整渲染,GPU 合成关闭的情况下滚动无明显掉帧。
5.2 Notebook 编辑与执行
核心闭环。新建 Untitled.ipynb,逐格输入并执行:
![notebook 执行中:[1] print(1) 输出 1,[2] 执行完成,[3] 正在编辑,Command 模式状态栏实时反映](https://i-blog.csdnimg.cn/img_convert/6790e0d5c0ef01426b024eefb9e911aa.jpeg)
In/Out 标记、单元格编号自增、模式指示(Command/Edit)、底部状态栏的 Cell n/n 与行列号都在工作——这些细节全部来自 Lab 前端本身,说明静态化部署没有破坏其状态管理。
再跑一段更复杂的:import time/os、函数定义、循环、以及一段生成 ASCII 大字的字符串代码:

import time、import os 成功、函数定义与调用成功、多行字符串拼接输出成功——Skulpt 内核的执行链路完整。
5.3 Python 文件编辑器
除了 notebook,Lab 的多文档能力也要验证。通过 Launcher 新建 Python File,写入同样的代码:

语法高亮、行号、编辑器/控制台双模式都正常。
5.4 菜单完整度
Electron 壳最容易出问题的就是原生菜单。实测 File 菜单完整展开:

从 New 到 Print 共 20+ 个菜单项,快捷键标注(Ctrl+Shift+L、Alt+W、Ctrl+Shift+Q…)一项不少——这些是 Lab 前端自绘的菜单,不是 OS 原生菜单,所以行为在 Electron 壳里是自洽的。
5.5 文件操作对话框

重命名对话框、输入框焦点、按钮响应全部正常。
5.6 About 弹窗

Help → About JupyterLab 弹窗正常显示——一个容易被忽略但很说明问题的细节:跑在鸿蒙 PC 上的确实是 JupyterLab 本尊,不是仿制的 UI。
六、已知限制(如实记录)
| 限制 | 说明 |
|---|---|
| 内核是 Skulpt,不是完整 CPython | numpy/pandas/matplotlib 等 C 扩展生态不可用;纯 Python 语法子集没问题 |
| GPU 合成默认关闭 | OHOS Electron 的硬件加速路径不稳定,实测关掉更稳 |
| 大 HAP 体积 | 528MB(Electron runtime + Lab 前端 + HNP),主要是 Electron 基础设施 |
| 三个工程踩了同一个签名坑 | DevEco 自动签名不回填 products[].signingConfig,每次 Fix 后要手动检查 |
| external 模式未在弱网验证 | JUPYTERLAB_EXTERNAL_SERVER_URL 连远程 Jupyter Server 的路径已实现,等待真机场景 |
七、复现路径
cd ohos_JupyterLab
# 1. 从 Notebook 工程同步 runtime(首次必做)
node scripts/bootstrap-from-notebook.mjs --with-hap
# 2. 打包 runtime 到 web_engine resfile
node pkg/ohos/build-package.mjs
# 3. 构建 HAP(需 DevEco / hvigor)
node pkg/ohos/build-hap.mjs
# 或直接:
cd ohos_hap && hvigorw assembleHap --mode module -p electron@default -p buildMode=debug
# 4. DevEco 自动签名后,检查并修正 products[].signingConfig = "default"
# 5. 安装
hdc install -r electron/build/default/outputs/default/electron-default-signed.hap
hdc shell aa start -a EntryAbility -b org.jupyter.lab.ohos
环境变量速查(JUPYTERLAB_* 优先,NOTEBOOK_* 兼容):JUPYTERLAB_OPENHARMONY=1、JUPYTERLAB_DISABLE_GPU=1、JUPYTERLAB_PORT / JUPYTERLAB_TOKEN / JUPYTERLAB_ROOT_DIR、JUPYTERLAB_OHOS_SERVER_MODE。
常见问题 FAQ
Q1:安装报 9568320 no signature file,但我明明签名了?
看产物文件名。如果是 electron-default-unsigned.hap,说明 build-profile.json5 里 products[].signingConfig 还是空串 ""——DevEco 自动签名只写入 signingConfigs 材料,不会回填这个引用。手动改成 "default" 重新构建即可。
Q2:装上之后白屏,什么都不显示?
九成是资源树放错了模块。Electron 应用资源必须放在 web_engine/src/main/resources/resfile/resources/app,放进 electron/(entry)模块的 resfile 不会被打包,构建也不报错。
Q3:能装 numpy / pandas 吗?
不能。默认内核是 Skulpt(浏览器内纯 JS 实现的 Python 子集),print、import time/os、函数定义这些纯语法没问题,但没有 CPython 的 C 扩展生态。需要科学栈时用 external 模式连远程 Jupyter Server(JUPYTERLAB_EXTERNAL_SERVER_URL)。
Q4:和 Jupyter Notebook 版冲突吗?能不能同时装?
能共存,前提是 Lab 的 HNP 包声明为 "type": "private"。如果两个都装时报 9568407,说明用了同名 public HNP(jupyterlab_python.hnp),把 Lab 侧改成 private 重新打包即可。
Q5:编译报 10705000,import lazy 不认识?
build-profile.json5 里出现了 compatibleSdkVersionStage: "beta1"。删掉这个字段,把 SDK 钉在 compatibleSdkVersion: "6.0.1(21)"。注意 DevEco 每次 Sync / 签名 Fix 都可能把它改回来,动过配置就要检查一遍。
Q6:界面有点卡,能开硬件加速吗?
不建议。OHOS Electron 的 GPU 合成路径目前不稳定,默认已通过 JUPYTERLAB_DISABLE_GPU=1 关闭。真机关 GPU 滚动无明显掉帧,属于可用状态;开 GPU 反而可能出现渲染异常。
更多推荐




所有评论(0)