JupyterLab Electron 鸿蒙 PC 适配全记录:从 Python 原生崩溃到 node-static 本地工作台

一、写在前面

欢迎加入鸿蒙 PC 开发者社区,共同打造开发者工具生态:鸿蒙 PC 开发者社区:https://harmonypc.csdn.net/

项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_JupyterLab

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

环境搭建文章:https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetype=blogdetail&sharerId=161286249&sharerefer=PC&sharesource=lbcyllqj&spm=1011.2480.3001.8118

这篇文章记录的是一次把 JupyterLab 适配到 HarmonyOS PC / OpenHarmony Electron 环境的完整过程。

JupyterLab 不是一个普通的 Electron 工具,也不是简单的静态网页。它本质上是一个交互式计算工作台,原始架构里包含:

JupyterLab 前端
  -> Jupyter Server
  -> Kernel / Terminal / Contents / Settings / Workspace 等服务

在桌面环境里,JupyterLab 通常由 Python 启动:

jupyter lab

然后浏览器访问本机的:

http://127.0.0.1:8888/lab

迁移到鸿蒙 PC 后,最开始的想法也是尽量保持这个结构:OpenHarmony Electron 负责开窗口,Python 负责启动 Jupyter Server,前端继续访问本地 127.0.0.1:8888

但实际真机运行时,Python 原生依赖链遇到了比较关键的问题:pyzmq / libsodium 相关 native extension 在设备上导入时发生崩溃,导致完整 Jupyter Server 路线暂时不可用。最后适配方案改成了更稳的一阶段方案:

HarmonyOS PC Electron HAP
  -> 启动 Electron 主进程
  -> 用 Node 在本机启动轻量 static server
  -> 服务 JupyterLab 静态资源
  -> 模拟一部分 Jupyter Server REST API
  -> 保留文件浏览、文本编辑、Markdown 编辑、工作区、设置等前端能力

也就是说,这次最终跑通的版本不是“完整 Python Kernel 版 JupyterLab”,而是一个更适合当前鸿蒙环境的 JupyterLab 本地文件工作台版本。它可以启动 JupyterLab 前端,可以创建、打开、保存文件,也可以使用 JupyterLab 原本的多标签、文件浏览器、编辑器、主题和工作区能力;但 Notebook 代码执行、Terminal、Console、Debugger、LSP 等强依赖 Python Kernel / 后端服务的能力在当前阶段先禁用。

在这里插入图片描述

二、先判断 JupyterLab 原始功能和适配边界

在适配之前,首先需要判断这个项目到底是什么类型。

JupyterLab 官方定位是 Project Jupyter 的下一代用户界面,它提供经典 Jupyter Notebook 中的 notebook、terminal、text editor、file browser、rich outputs 等能力,并把这些能力组织成一个可扩展的工作台。

从功能上拆开看,原始 JupyterLab 主要包括:

  • Notebook 文档和 cell 执行
  • Python / R / Julia 等 Kernel 管理
  • Code Console 交互式控制台
  • Terminal 终端
  • 文件浏览器
  • 文本和代码编辑器
  • Markdown、JSON、CSV、图片、PDF 等查看器
  • Settings 设置系统
  • Workspace 工作区布局保存
  • 命令面板、菜单、快捷键
  • 主题和插件系统
  • Debugger 调试器
  • LSP 语言服务
  • 实时协作

这些能力并不是同一种技术难度。适配时必须把它们分成两类:

适合先保留的能力:
文件浏览、文件创建、文本编辑、Markdown 编辑、JSON/CSV/图片查看、主题、设置、工作区、多标签布局。

暂时不适合原样保留的能力:
Notebook 代码执行、Kernel、Terminal、Console、Debugger、LSP、实时协作。

原因也很直接:前一类能力主要依赖 JupyterLab 前端资源和文件 API;后一类能力依赖 Python Jupyter Server、ZeroMQ、Kernel 协议、WebSocket、PTY、语言服务器等完整后端链路。

这次适配的关键不是“把所有功能都宣称支持”,而是先让应用在鸿蒙 PC 上稳定活下来,并保留最容易落地、最有实际使用价值的一部分功能。


三、建立 OpenHarmony Electron HAP 工程

适配时新增了两个主要目录:

jupyterlab-main/
├── pkg/ohos/
│   ├── build-package.mjs
│   ├── build-hap.mjs
│   └── runtime/
│       ├── assets/
│       ├── package.json
│       └── src/
│           ├── html/
│           └── js/
└── ohos_hap/
    ├── AppScope/
    ├── electron/
    └── web_engine/

其中 ohos_hap/ 是 OpenHarmony Electron HAP 工程,负责应用身份、签名、模块配置、原生 Electron 运行库和最终 HAP 产物。

pkg/ohos/ 是 JupyterLab 自己的鸿蒙适配层,主要负责:

  • 生成 Electron 主进程资源
  • 把运行时代码同步到 HAP 的 resources/app
  • 复制 JupyterLab 静态资源和必要依赖
  • 根据环境变量选择运行模式
  • 调用 Hvigor 构建 HAP

生成后的 Electron 应用资源会放到:

ohos_hap/web_engine/src/main/resources/resfile/resources/app

这个目录就是 OpenHarmony Electron runtime 加载 Node/Electron 应用的位置。和普通桌面 Electron 的 resources/app 思路类似,只是在 HAP 工程里的路径不同。

构建脚本主要有两个:

pkg/ohos/build-package.mjs
pkg/ohos/build-hap.mjs

build-package.mjs 负责同步资源、写入入口、准备运行时文件。build-hap.mjs 负责先调用 build-package.mjs,再调用 DevEco / Hvigor 生成 HAP。

常用构建命令如下:

cd ~/XM/jupyterlab-main
JUPYTERLAB_OHOS_LOCAL_PYTHON=1 node pkg/ohos/build-hap.mjs

最终产物位于:

ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap

在这里插入图片描述

四、第一版方案:尽量保持原始 Python Jupyter Server 架构

一开始的适配方案比较接近原始 JupyterLab:

Electron 主进程
  -> spawn Python
  -> python -m jupyterlab
  -> 等待 127.0.0.1:8888/lab 可访问
  -> BrowserWindow 加载 /lab?token=...

这条路线的优点很明显:如果跑通,功能最完整。Notebook、Kernel、Terminal、Console、Debugger 等能力都可以尽量保持原版。

为此,运行时代码中实现了 Python 启动逻辑:

pkg/ohos/runtime/src/js/jupyterlab.js

大致流程如下:

resolvePythonPath()
  -> resolveRootDir()
  -> prepareBundledPyLibs()
  -> writePythonBootstrap()
  -> spawn python -m ohos_jupyter_bootstrap jupyterlab
  -> poll http://127.0.0.1:8888/lab
  -> load BrowserWindow

同时为了适配鸿蒙上 Python native extension 的加载限制,还做过一层 bootstrap:

  • 把纯 Python 包复制到可写沙箱目录
  • .so native extension 放到 HAP native library 目录
  • 通过 sitecustomize / import hook 让 Python 导入 native 模块
  • 设置 LD_LIBRARY_PATH
  • 写入 python-native-extensions.json

这套思路本身是合理的,也适合后续继续深挖。但在真机上,问题出在更底层的 native 依赖。


五、真机问题:pyzmq / libsodium 导入阶段崩溃

Jupyter Server 和 Kernel 通信依赖 pyzmq。在设备上启动 Python 路线时,日志里可以看到 native extension 加载到了 zmq.backend.cython._zmq 相关位置,并继续加载 libsodiumlibzmq 等依赖。

问题是,在这个阶段应用发生了原生崩溃,表现为 Jupyter Server 无法真正启动起来,Electron 侧只能等待或者显示错误页。

这个问题和普通前端白屏不一样。普通白屏可以从 HTML、JS、CSS、接口 404 这类方向排查;但这里已经进入了 Python native extension 和系统动态库加载层面,继续沿着“完整本地 Python Jupyter Server”路线硬修,会变成:

Python for OpenHarmony
  -> CPython ABI
  -> pyzmq
  -> libzmq
  -> libsodium
  -> libstdc++ / libgcc
  -> OpenHarmony 动态库加载限制

这条链路任何一环不稳定,JupyterLab 都无法启动。对于第一阶段目标来说,继续在这里死磕并不划算。

于是适配策略做了调整:不再把 Python/Jupyter Server 当成默认启动路径,而是保留为调试 fallback。

现在如果确实要强制走 Python 路线,可以通过环境变量打开:

JUPYTERLAB_OHOS_SERVER_MODE=python

或者:

JUPYTERLAB_OHOS_FORCE_PYTHON=1

默认情况下,鸿蒙设备上走新的 node-static 模式。

六、第二版方案:用 Node 实现 JupyterLab 静态本地服务

确认 Python native 依赖是主要风险后,适配方案换成了新的结构:

Electron 主进程
  -> startStaticLabServer()
  -> Node http server 监听 127.0.0.1:8888
  -> 服务 JupyterLab static / themes / schemas
  -> 实现最小 Jupyter Server 兼容 API
  -> BrowserWindow 加载 http://127.0.0.1:8888/lab

核心文件是:

pkg/ohos/runtime/src/js/staticLabServer.js

这个文件做了几件关键工作。

第一,服务 JupyterLab 前端资源:

/lab
/static/*
/themes/*
/schemas/*
/files/*

第二,实现 JupyterLab 前端启动需要的基础 API:

/api
/api/status
/api/me
/api/kernelspecs
/api/kernels
/api/sessions
/api/terminals
/api/contents
/lab/api/settings
/lab/api/workspaces
/lab/api/translations
/api/nbconvert
/api/licenses
/api/config/<section>
/api/events

第三,实现 contents 文件 API,让 JupyterLab 前端可以创建、读取、保存、删除、重命名、复制文件,并支持 checkpoint 相关请求。

这部分是当前 node-static 方案最有实际价值的地方。因为它让 JupyterLab 不只是“界面能打开”,而是真正具备了本地文件工作台能力。

第四,生成 JupyterLab HTML 页面,并补齐前端启动所需的 page config。

这里踩过一个非常典型的坑:JupyterLab 前端主包启动时会读取:

pageConfig.federated_extensions

如果这个字段不存在或类型不对,前端会在启动阶段报错,表现为窗口白屏。最终修复方式是在 page config 里明确写入:

federated_extensions: []

这个点很小,但非常关键。很多大型前端应用在迁移到自定义 server 时,真正的问题并不是资源没加载,而是后端模板注入的配置字段不完整。

在这里插入图片描述

七、主进程运行模式切换

最终主进程里保留了两条路线:

默认路线:
OpenHarmony -> node-static local server

调试路线:
JUPYTERLAB_OHOS_SERVER_MODE=python
或 JUPYTERLAB_OHOS_FORCE_PYTHON=1
-> Python Jupyter Server

核心切换逻辑在:

pkg/ohos/runtime/src/js/jupyterlab.js

简化理解如下:

function startLocalServer() {
  if (openHarmony && !shouldForcePythonLocalServer()) {
    startNodeStaticLocalServer();
    return;
  }
  startPythonLocalServer();
}

这样做有两个好处。

第一,默认体验稳定。用户安装 HAP 后,不会因为 Python native extension 崩溃而直接白屏。

第二,后续仍然保留继续研究完整 Kernel 方案的入口。如果后面 pyzmq、libsodium 或 Python runtime 的问题解决,可以重新打开 Python 模式验证,而不需要推翻整个适配工程。

在 node-static 模式下,应用会明确禁用或空实现这些能力:

Kernel execution
Terminal
Console
Debugger
LSP
Running sessions
Extension manager

但会保留:

JupyterLab 主界面
文件浏览器
文本文件创建和保存
Markdown 文件编辑
Settings API
Workspace API
主题资源
多标签工作区
静态资源加载
基础 contents API

这是一种更符合当前阶段的取舍:先把“能用的部分”跑稳,再讨论“完整计算环境”。


八、真机安装和启动验证

构建完成后,使用 hdc 安装 HAP:

HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc

$HDC shell aa force-stop org.jupyter.jupyterlab.ohos
$HDC shell hilog -r
$HDC shell rm -f /data/app/el2/100/base/org.jupyter.jupyterlab.ohos/files/jupyterlab-runtime.log

$HDC install -r ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap
$HDC shell aa start -a EntryAbility -b org.jupyter.jupyterlab.ohos -m electron

启动后可以先验证本地服务是否真的起来:

$HDC shell "wget -O - http://127.0.0.1:8888/lab | head -c 500; echo"
$HDC shell "wget -O - http://127.0.0.1:8888/api/contents | head -c 500; echo"

如果能看到 HTML 片段和 contents JSON,说明 node-static server 已经正常工作。

然后拉取运行日志:

$HDC shell cat /data/app/el2/100/base/org.jupyter.jupyterlab.ohos/files/jupyterlab-runtime.log | tail -700

正常日志里应该能看到:

Starting OpenHarmony node-static local server.
JupyterLab OpenHarmony node-static runtime
Server URL: http://127.0.0.1:8888
Starting application in workspace: "default"
GET /api/kernelspecs -> 200
GET /api/me -> 200
GET /lab/api/settings -> 200
GET /api/contents -> 200

这一步能确认几个关键点:

  • HAP 可以安装
  • OpenHarmony Electron 可以启动
  • Electron 主进程可以启动 Node HTTP server
  • 设备内部可以访问 127.0.0.1:8888
  • JupyterLab 前端资源可以加载
  • 基础 Jupyter Server API 模拟成功

在这里插入图片描述

九、验证文件创建、编辑和保存

JupyterLab 前端真正可用,不能只看首页。还要验证文件 API 是否闭环。

当前 node-static server 已经实现了 /api/contents 的主要行为:

GET     /api/contents
GET     /api/contents/<path>
POST    /api/contents
PUT     /api/contents/<path>
PATCH   /api/contents/<path>
DELETE  /api/contents/<path>
POST    /api/contents/<path>/checkpoints
GET     /api/contents/<path>/checkpoints

所以可以在真机界面上测试:

  1. 打开 JupyterLab。
  2. 在文件浏览器中点击新建文本文件。
  3. 输入几行内容。
  4. 保存文件。
  5. 关闭 tab 后重新打开,确认内容仍然存在。
  6. 再创建一个 Markdown 文件,验证 Markdown 编辑器是否能正常打开。

从日志里可以看到类似请求:

POST /api/contents -> 201
GET /api/contents/untitled.txt -> 200
PUT /api/contents/untitled.md -> 200
POST /api/contents/untitled.md/checkpoints -> 201

这说明 JupyterLab 的前端文档管理流程已经可以跑通。虽然 Kernel 还不可用,但作为一个本地文件编辑和查看工作台,已经具备基本可用性。

在这里插入图片描述

在这里插入图片描述

十、这次适配中几个关键问题的复盘

1. 不要把 JupyterLab 当成普通静态网页

JupyterLab 的前端虽然可以通过静态资源加载,但它启动时会立刻请求一批 Jupyter Server API。如果只把 static/ 目录丢给 Electron 加载,大概率会遇到白屏、插件启动失败、设置加载失败、工作区无法恢复等问题。

所以 node-static 方案不是简单的 serve static files,而是要模拟一组最小 Jupyter Server API。

2. page config 字段必须补齐

JupyterLab 前端依赖后端注入的 pageConfig。其中有些字段看起来不是业务逻辑,但缺了会直接影响 bootstrap。

这次比较关键的字段是:

federated_extensions: []

没有它时,前端主包启动阶段会对这个字段调用 .map(),字段异常就会导致白屏。

3. Python native 崩溃要及时换路线

最开始沿着 Python/Jupyter Server 路线做,是因为这是最接近原版的方案。但真机上遇到 pyzmqlibsodium 这类 native 崩溃后,继续硬修会把问题扩大到完整 Python native 生态。

在这种情况下,先换成 node-static 路线是更稳的工程选择。

十一、当前能力和后续方向

当前适配后的 JupyterLab 鸿蒙 PC 版本可以实现:

  • HAP 构建、签名、安装、启动
  • OpenHarmony Electron 窗口加载
  • Node 本地 server 启动
  • JupyterLab 前端启动
  • 文件浏览器基础展示
  • 文本/Markdown 文件创建、读取、保存
  • settings API
  • workspace API
  • themes / schemas / static 资源加载
  • 基础 diagnostics 日志

当前暂时禁用或不可用:

  • Notebook 代码执行
  • Python Kernel
  • Terminal
  • Code Console
  • Debugger
  • LSP
  • 实时协作
  • 依赖完整 Jupyter Server 的扩展

后续可以继续探索三个方向。

第一,远程 Kernel / 远程 Jupyter Server 模式。鸿蒙端只作为 JupyterLab 客户端,Kernel 跑在 Mac、服务器或 DevBox 中。

第二,修复 Python native 链路。继续研究 pyzmq、libsodium、libzmq 在 OpenHarmony 上的 ABI、动态库加载和崩溃原因,最终恢复完整本地 Jupyter Server。

第三,轻量执行环境。对于部分简单脚本,可以考虑 WebAssembly、Pyodide、JavaScript kernel 等方式,但这会和标准 Jupyter Kernel 体系有差异,需要单独设计。


十二、总结

这次 JupyterLab 鸿蒙 PC 适配最大的收获,不是简单把一个页面塞进 HAP,而是重新判断了一个复杂开发者工具在新平台上的落地边界。

一开始我们尝试保持原始架构,让 Electron 启动 Python Jupyter Server,再由 JupyterLab 前端连接本地服务。这条路最完整,但在真机上被 Python native 依赖链挡住了,尤其是 pyzmq / libsodium 一类底层库崩溃。

最后切换到 node-static 本地服务方案,绕开了 Python native 崩溃点。这个方案牺牲了 Kernel 和 Terminal 等完整计算能力,但换来了一个稳定可启动、可浏览文件、可编辑保存文件的 JupyterLab 本地工作台。

对大型 Electron / Web 开发者工具迁移到鸿蒙 PC 来说,这个过程也提供了一个通用经验:

先识别原项目的核心依赖链,
再把强后端能力和纯前端/文件能力拆开,
优先跑通可独立落地的部分,
最后再逐步恢复更重的运行时能力。

JupyterLab 的完整能力仍然值得继续适配,但当前这个阶段,先让它在鸿蒙 PC 上稳定打开、显示、管理文件、保存内容,已经是一次比较关键的工程落点。

Logo

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

更多推荐