Python 编辑器上架鸿蒙 PC:从 Thonny 真编译移植到 UI 升级版的完整技术解析

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

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

适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_thonny

源于开源 Python IDE Thonny,游于鸿蒙——安装即用,开启你的 Python 之旅。

一、写在前面:这次上架的是「UI 升级版」

「青蟒 Python 编辑器」(英文名 Thonny Native,包名 org.thonny.native)已经在 HarmonyOS PC 应用市场上架。这里首先要向已经用过 V1.0 的读者说明一点:本次上架的版本是一个 UI 升级版,基于 V1.0 的架构与样式做了全面改进——底层解释器链路(交叉编译 CPython 3.12.9 + 上游 Thonny 4.1.7 源码)保持不变,而前端从「验收工具页」升级成了一个完整的 IDE 外壳:多标签代码编辑器、Python 语法高亮、交互式 Shell、变量面板、文件打开/保存、关于对话框,全部重做。

具体来说,两个版本的关系是这样的:

维度V1.0(1.0.5,内部代号 code-runner)上架版(1.1.0,内部代号 ide-shell)
定位移植链路验收工具可日常使用的入门级 Python IDE
界面两个页签:「运行日志」+「Python 执行器」多标签编辑器 + Shell 面板 + 变量面板 + 菜单栏
启动内容预置示例代码启动即空白 untitled.py,不给用户预置任何东西
语法高亮无逐行词法着色,配色逐项对照上游 Thonny 主题
交互单次执行,回显 stdout/stderr交互式 Shell,支持 input()、exit()、%Run
变量观察无变量面板,每次执行后自动刷新
文件无打开 / 保存 / 另存为 / 多标签(DocumentViewPicker,零权限)
底层交叉编译 CPython 3.12.9 静态嵌入 + Thonny 4.1.7 源码完全相同

V1.0 验收:P2 SUCCESS

换句话说,V1.0 回答的问题是「真实的 CPython 和真实的 Thonny 能不能在鸿蒙上跑起来」;而上架版回答的问题是「跑起来之后,能不能像一个真正的 IDE 一样被初学者使用」。这篇文章会把两个版本的技术方案一次讲透:为什么坚持「真编译」路线、静态嵌入解释器的完整链路、UI 升级版每一块功能的实现细节,以及一路上踩过的坑。
青蟒Python编辑器

二、为什么是「真编译」

市面上的「XX 平台移植 Python」方案,相当一部分实际做法是:自己用目标平台的 UI 框架画一个编辑器界面,再加一个远端解释器或者干脆做个假的执行反馈。这种做法 UI 看起来像,但解释器不是真的,生态(pip、第三方库、Thonny 的教学功能)更是无从谈起。

我们在这个项目上立的第一条红线就是:不做 UI 仿制,做真编译移植。目标是让真实的运行时栈在鸿蒙上跑起来:

  1. 真实的 CPython 3.12.9——不是套壳、不是 JS 模拟,而是用 OHOS NDK 的 clang 交叉编译出来的 aarch64 原生解释器;
  2. 真实的上游 Thonny 4.1.7 源码——直接 vendor 上游 GitHub 仓库的 Python 包,不改一行逻辑;
  3. 验收标准不看出错与否,看输出内容——PyRun_SimpleString 返回 0 不代表成功,必须 stdout 里出现约定标记(Hello from real CPython on OHOS!、thonny 4.1.7、common_ok True)才算通过。

这条路线在工程上要回答三个问题:

第一,鸿蒙应用沙箱不允许 exec。 应用的 filesDir 是 noexec 挂载,把 python3.12 可执行文件丢进沙箱再拉子进程这条路是死的。所以解释器不能以独立进程形态存在,只能以静态库形式链进 NAPI 的 .so,在应用进程内直接调用 Py_InitializeEx / PyRun_SimpleString。

第二,标准库和 Thonny 源码不是代码,是数据。 lib/python3.12 下几百个 .py 文件本身不需要执行权限,Python 解释器只是把它们当文本读进来编译成字节码。所以它们可以作为纯数据随 HAP 分发:打包成 zip 放在 rawfile 里,首次启动解压到 filesDir/ohos_python/,然后通过 PYTHONHOME / PYTHONPATH 指过去。

第三,C 扩展模块怎么办。 静态链接的 libpython 加载不了 lib-dynload 下的 math.so、_ssl.so 这类动态扩展(dlopen 找不到共享的 libpython 符号)。这是一个诚实的取舍:交叉编译阶段裁掉 _ssl、_hashlib、_ctypes、_tkinter、_sqlite3 等依赖缺失或意义不大的模块,保证纯 Python 路径 100% 可用。对入门教学场景(这也是 Thonny 的定位)来说,这个裁剪是可以接受的,并且我们在文档里明确标注了这一限制,而不是含糊其辞。

这三个决策确定了整个项目的形态,后面所有工作都是在这个骨架上添砖加瓦。

三、整体架构:一张图说清楚

在这里插入图片描述

分层来看:

  • ArkTS 层(Index.ets,约 1400 行):负责运行时 zip 的解压与校验、IDE 界面的全部渲染与交互,以及通过 NAPI 桥调用解释器。上架版的全部 UI 升级都发生在这一层。
  • NAPI 层(thonny_napi.cpp,约 390 行):极薄的桥。对外只暴露 4 个方法:getBuildInfo / setPythonHome / runTest / runCode。内部持有 g_pyInited 标志保证解释器全生命周期只初始化一次,所有执行入口共用一把 mutex 串行化。
  • CPython 层:交叉编译产物 libpython3.12.a(33MB)以 -Wl,--whole-archive 整体链入 NAPI .so。whole-archive 是必须的——静态库链接默认只取「被引用的目标文件」,而解释器的很多模块是运行时才被 import 的,不 whole-archive 会链接期看着没问题、运行期 import 即崩。

CMakeLists 的核心就这几行,值得完整贴出来:

set(PYTHON_INC "${CMAKE_CURRENT_SOURCE_DIR}/python_inc")
set(PYTHON_LIB "${CMAKE_CURRENT_SOURCE_DIR}/prebuilt/libpython3.12.a")

add_library(thonny_native SHARED thonny_napi.cpp)
target_include_directories(thonny_native PRIVATE "${PYTHON_INC}")

# whole-archive: pull full interpreter from static archive into the NAPI .so
target_link_libraries(thonny_native PUBLIC
    -Wl,--whole-archive
    "${PYTHON_LIB}"
    -Wl,--no-whole-archive
    libace_napi.z.so
    libhilog_ndk.z.so
    libc++.a
    libm.a
    libdl.a
)

还有一个细节:add_library(thonny_native ...) 而不是 add_library(libthonny_native ...)——前者产物是 libthonny_native.so,后者会变成双 lib 前缀的 liblibthonny_native.so,ArkTS 侧 import thonnyNative from 'libthonny_native.so' 直接加载失败。这是鸿蒙 NAPI 开发的高频坑,我们把它写进了 README 的必读清单。

四、V1.0 回顾:先用最朴素的界面证明链路

在讲 UI 升级版之前,有必要回顾 V1.0 做了什么,因为上架版的所有底层逻辑都原封不动继承自它。

V1.0 的界面非常朴素:一个「运行日志」页负责 P2 验收(点 Run Test 按钮,跑一段固定的验证脚本),一个「Python 执行器」页让用户输入任意 Python 代码并回显结果。

V1.0 启动:P2 Ready

点下 Run Test 后,状态栏从 P2 Ready 变成绿色的 P2 SUCCESS,日志里依次出现:

Hello from real CPython on OHOS!
python 3.12.9
thonny 4.1.7
common_ok True
[P2] SUCCESS: real CPython imported real Thonny 4.x source (headless).

V1.0 验收:P2 SUCCESS

这段验证脚本是精心设计的「最小充分集」:

  • import sys; print(sys.version) —— 证明解释器本体活着,而且是 3.12.9;
  • import thonny; print(thonny.get_version()) —— 证明上游 Thonny 源码在 site-packages 里被真实加载;
  • import thonny.common —— 证明 Thonny 的内部模块依赖链可以走通;
  • print(1+1)、三行 count、Done. —— 证明 stdout 捕获管道逐行可靠,不是碰巧拿到一行输出。

C++ 侧的判定逻辑也很直接:报告字符串里必须同时命中 PyRun_SimpleString rc=0、Hello、thonny 4.、common_ok True、count、Done 全部标记,才追加 [P2] SUCCESS。任何一项缺失都会给出具体的失败提示,甚至包括「runtime zip 没解压对,缺哪个文件」的 hint。

执行器页则是把 runCode() 接到输入框,可以跑任何纯 Python 代码。下面这张图是跑了一段斐波那契平方数列的输出,500 多个数字一气呵成:

V1.0 执行器实测

V1.0 在 HUAWEI MateBook Pro(HarmonyOS 7.0.0(26.0.0))真机上完成全部验收:应用 367ms 拉起、P2 SUCCESS、7 行示例代码 2ms 跑完、退出重进日志出现 reuse existing(解压产物正确复用)。这个版本证明了「交叉编译解释器 + 上游 Thonny 源码」的链路成立——但它毕竟只是一个验收工具,界面离「能给初学者写代码」还差得远。UI 升级版要补的正是这一段。

五、UI 升级版:每一处样式改动都有出处

上架版最重要的设计原则是:UI 可以用 ArkUI 重写,但视觉规范不许自创。Thonny 是一个有十多年积淀的教学 IDE,它的配色、布局是被无数初学者验证过的。所以我们把上游源码翻出来,逐项对照着做:

  • 窗口框架色取自 base_ui_themes.py 的 clam 主题:菜单栏/工具栏 #dcdad5、未选中标签页 #cfcdc8、边框 #bab5ab、悬停 #eeebe7;
  • 编辑器底色取 Thonny 标志性的米黄 #ffffdc;
  • 语法高亮取 base_syntax_themes.py 的 default_light 主题:关键字/内建 #7f0055、数字 #B04600、字符串 DarkGreen、注释 DarkGray、def/class 定义的名称 DarkBlue 加粗;
  • Shell 的提示符紫 #A020F0、stdin 蓝 #0000FF、stderr 红 #CC0000、%Run 行用超链接蓝 #3A66DD;
  • 连 Shell 顶部的黄色通知条(#ffff99,对应上游 shell.py 的 notice 条)都保留了。

上架版主界面长这样——上面是 clam 风格的菜单栏与标签页,中间是米黄底的编辑器与行号槽,下面是 Shell 面板:

上架版:编辑器 + Shell

截图中跑的是一段去重函数,Shell 里 >>> 提示符、紫色的 %Run untitled.py(对应上游 Thonny 执行脚本时的 magic 命令样式)、结果 [1, 2, 3, 4]、底部新的 >>> ,交互形态与桌面版 Thonny 的 Shell 一致。

5.1 多标签编辑器与行号槽

编辑器按「标签页 + 行号槽 + 编辑区」组织。每个 EditorTab 持有 id / name / path / content / dirty 五个字段:path 为空表示未保存的新文件,dirty 驱动标签页标题前的 * 号。打开一个已经在标签页里存在的文件时直接切过去,不重复开页。

行号槽有一个容易被忽略的细节:空白区域也要补行号。大多数粗制编辑器只在有内容的地方画行号,往下滚动就断了。我们的做法是把内容行数和视口容量取大(editRows()),行号槽按 22vp 的固定行高一直铺到视口底部,和正规 IDE 的观感一致:

editRows(): number {
  const lines = this.editContent.split('\n').length;
  return Math.max(lines, Math.floor(this.editorViewportH / EDITOR_ROW_H));
}

editorLines(): string[] {
  const raw = this.currentTab()!.content.split('\n');
  if (raw.length > 1 && raw[raw.length - 1].length === 0) {
    raw.pop();   // 末尾换行不产生幽灵行
  }
  const minRows = Math.max(raw.length, Math.floor(this.editorViewportH / EDITOR_ROW_H));
  while (raw.length < minRows) {
    raw.push('');  // 空白区域也补行号
  }
  return raw;
}

5.2 语法高亮:一个 80 行的逐行 tokenizer

语法高亮没有引入任何第三方库,就是一个手写的逐行词法着色器。原因是 Python 的单行着色规则足够简单(注释、字符串、数字、标识符四类 token),而引入依赖要付出包体和兼容代价。实现上按字符扫描,处理四类分支:

tokenizeLine(line: string): Token[] {
  // 1. '#' 起始 → 整行剩余部分都是注释(SYN_COMMENT)
  // 2. 空白 → 原样保留
  // 3. 引号 → 扫到配对引号(处理 '\' 转义),整段标 SYN_STRING
  // 4. 数字 → 扫十六进制/八进制/二进制前缀、小数点、下划线,标 SYN_NUMBER
  // 5. 标识符 → 查两张表:
  //    prevWord === 'def' || 'class'  → SYN_DEFINITION 加粗(函数/类定义名)
  //    命中 KEYWORDS(37 个关键字)    → SYN_KEYWORD 加粗
  //    命中 BUILTINS(58 个内建)      → SYN_BUILTIN
  //    其他                            → 默认前景色
}

其中「def/class 后面的标识符单独着色并加粗」是靠维护一个 prevWord 状态实现的——这恰恰是上游 Thonny 编辑器里的定义名高亮行为,也是很多简易高亮器漏掉的细节。所有色值常量都注释了出处(对应上游主题文件的哪个字段),方便将来跟着上游版本同步。

5.3 Shell 面板:input() 和 exit() 是两个深坑

Shell 是上架版工作量最大的一块。交互式 Shell 要处理三类输入:

  1. 普通语句——直接 runCode() 执行,回显 stdout/stderr;
  2. 脚本中的 input()——嵌入式解释器没有交互 stdin,input() 会直接 EOFError;
  3. 用户敲 exit() / quit()——真让它生效,整个应用进程就没了。

第 2、3 类是真正的深坑,值得展开讲。

input() 的方案:stdin 重放 + 哨兵协议。 思路是:脚本里出现的 input(),由我们在执行前覆盖 builtins.input 来接管。每次执行前,把 Shell 里已收集到的 stdin 行注入 __stdin_lines__ 列表;有剩余行就弹出返回(实现重放,比如脚本里连续两个 input(),用户提前把两行都敲好);没有剩余行时,打印一个哨兵 __NEED_INPUT__:<prompt> 并抛出 _NeedInput 异常。ArkTS 侧解析 stdout 看到哨兵后,把提示符显示到 Shell 输入框上方、置 awaitingStdin 状态;用户下一次在输入框敲的内容不再当语句执行,而是收进 stdinAccum 并带着全部 stdin 重新跑一遍脚本。

这里有一个差点造成「点击运行即闪退」的致命细节:_NeedInput 必须继承普通 Exception,绝不能用 SystemExit。因为 C++ 包装层的 PyRun_SimpleString 只能兜住 except Exception,而 SystemExit 会逃逸到 CPython 的 handle_system_exit → Py_Exit → exit()——直接把宿主 App 进程杀掉,且不留任何可见报错。同样的道理,Shell 里用户敲 exit()/quit(),我们用自定义 _ScriptExit 异常兜住,stdout 里输出 __SCRIPT_EXIT__ 哨兵,ArkTS 侧抑制其 traceback 并温和地结束本次脚本。这段防御逻辑的完整注释就写在 Index.ets 里:

/**
 * 嵌入式解释器没有交互 stdin,input() 会 EOFError。做法:运行前把已收集的
 * stdin 行注入 __stdin_lines__ 并覆盖 builtins.input —— 有剩余行就弹出返回;
 * 没有时打印 __NEED_INPUT__:<prompt> 哨兵并 raise _NeedInput(普通 Exception,
 * ⚠️ 绝不能用 SystemExit:PyRun_SimpleString 里逃逸出包装层 except Exception
 * 的 SystemExit 会走 handle_system_exit → Py_Exit → exit() 直接杀掉整个进程,
 * 表现为点击运行即闪退)。
 */

%Run 的仪式感。 上游 Thonny 执行当前文件时会在 Shell 打一行 %Run 文件名。我们保留了这个 magic 命令的展示(超链接蓝),它既是情怀,也帮助从桌面版 Thonny 过来的用户建立「我在跑的是哪个文件」的心智模型。

5.4 变量面板:用 JSON 协议从解释器里「偷」全局变量

上架版:变量面板

右边这张截图里,变量面板列出了 fib_recursive、fib_iterative、fib_generator、lru_cache、n、unique、thonny、ver 等变量及其 repr 值。实现方式很轻:每次执行结束后,往解释器塞一段固定的内省代码——

import json as _json
_v = {}
for _k, _val in list(globals().items()):
    if _k.startswith('_'):
        continue
    try:
        _v[_k] = repr(_val)
    except Exception:
        _v[_k] = '<unrepr>'
print('VARS_JSON:' + _json.dumps(_v))

stdout 里以 VARS_JSON: 开头的那一行就是完整的变量快照,ArkTS 侧 JSON.parse 后渲染成表格。有两个工程细节:

  • 过滤注入物。解释器初始化和我们的包装层会往全局命名空间塞东西(site-packages 路径注入用的 p、异常路径 import 的 traceback、stdin 覆盖的 input/exit/quit),这些不是用户变量,全部过滤;
  • repr 失败兜底。某些对象的 repr 会抛异常(比如重写了坏 __repr__ 的类),用 <unrepr> 占位,绝不能让变量面板把执行流搞挂。

截图中还能看到三个标签页:素数.py、波那契数列.py、untitled.py,以及 Shell 里同一份斐波那契代码分别以递归(@lru_cache)、迭代、生成器三种方式跑出 前 10 项 的对比输出——这正是教学场景里最常见的用法。

5.5 文件系统:全程 DocumentViewPicker,零权限

打开、保存、另存为全部走 DocumentViewPicker。这个选择有两个理由:

  1. 零权限。picker 是用户授权制——用户在系统文件选择器里选中哪个文件、保存到哪个目录,应用才拿到对应 URI 的临时读写权,module.json5 的 requestPermissions 可以保持空数组。对一个教学工具来说,「要的权限越少,家长和老师越放心」。
  2. 编码兜底。读取时先看 BOM,有 UTF-8 BOM 就剥掉再解码;无 BOM 的内容先按 UTF-8 解,一旦发现替换符 U+FFFD 再回退 GB18030 重解——国内初学者的 .py 文件里有相当比例是老编辑器存出的 GBK 编码,这个兜底能省掉大量「打开全是乱码」的客服问题。

保存时用 DocumentSaveOptions.newFileNames 预填当前文件名,保存后标签页名跟随用户在对话框里实际起的名字,避免出现「文件已经叫 foo.py 了,标签还挂着 untitled.py」的割裂感。

5.6 关于对话框与启动策略

关于面板分层排版:官方 logo、名称、副标题、题句「一条青蟒,游于鸿蒙;一行代码,万物生长」、致谢 From Thonny、包名与构建号。上游归属必须在产品里可见——Thonny 由爱沙尼亚塔尔图大学维护(MIT),CPython 归 PSF(PSF License),我们在 README 和关于面板里都做了明确标注。

另外一个小改动体现产品思路:V1.0 启动时预置了示例代码方便验收,上架版启动即空白 untitled.py——一个上架的应用不应该替用户决定第一屏写什么。

六、运行时准备:marker 协议与「复用」判定

UI 之外,上架版还沉淀了一套可靠的运行时部署逻辑。rawfile 里的 ohos_python_stdlib.zip(stdlib + Thonny site-packages)在首次启动时解压到 filesDir/ohos_python/,但「首次」的判定不能靠感觉,我们用了一个 marker 协议:

const marker = `${this.pythonHome}/.${RUNTIME_MARKER}`;   // .READY_P2_1_0_3
const reusable =
  pathOpenable(osPy) &&          // lib/python3.12/os.py 必须真实可打开
  pathOpenable(thVer) &&         // site-packages/thonny/VERSION 必须存在
  pathExists(libDir) &&
  pathExists(marker) &&
  readTextFile(marker).indexOf(RUNTIME_MARKER) >= 0;

四重校验:目录在、关键文件在且可打开(不只是 stat 到)、marker 文件在、marker 内容匹配当前版本。任何运行时内容变更都要 bump RUNTIME_MARKER,老 marker 在下次启动时被 invalidateOldRuntime() 主动删除,触发完整重解压。这套机制防的是「zip 布局变了但沙箱里还留着上版本的残树」——V1.0 时代我们吃过亏:os.py 解压出来是空文件,解释器启动后 import os 直接失败,而表面上一切「看起来都在」。所以校验必须用「真实打开读一下」而不是「文件存在」,这就是 pathOpenable 和 pathExists 两个函数并存的原因。

解压完成后调 setPythonHome(pythonHome),C++ 侧会做一次同源的探测(home_dir / lib_dir / os.py / thonny 四项 + 目录抽样列举)并把结果返回给 ArkTS,任何一项异常都会在界面上弹出红色通知条而不是静默失败。最后跑一句 import sys; print(sys.version) 把 Python 版本横幅打到 Shell 顶部——等价于桌面版 Thonny Shell 的第一行。

七、构建与复现:两条路线

为了让任何人都能复现,仓库里备了两条构建路线:

路线 B(默认推荐,免交叉编译):预编译产物 prebuilt/libpython3.12.a(33MB)和 python_inc/(214 个头文件)直接入库,clone 后即可构建 HAP,不碰交叉编译。

路线 A(可选,自己编 CPython):native/build_cpython_ohos.sh 一键跑通。要点是先编 host python 3.12,再 configure --host=aarch64-unknown-linux-musl(config.sub 认 musl,OHOS 的 libc 与 musl 行为一致),实际目标由 OHOS clang 的 --target=aarch64-unknown-linux-ohos 加 sysroot 决定。脚本里处理了三个坑:构建目录放 /tmp 防沙箱写入问题、OHOS_PLATFORM_TRIPLET_PATCH 修正 multiarch 三元组、ac_cv_header_libintl_h=no 防假 libintl.h 污染 _localemodule。

Windows 机器上一条命令出包(工程根的 build_hap_tmp.cmd):

cd /d ...\ohos_Thonny_native\ohos_hap
set "DEVECO_SDK_HOME=C:\Program Files\Huawei\DevEco Studio\sdk"
set "PATH=C:\Program Files\Huawei\DevEco Studio\jbr\bin;%PATH%"
call ...\hvigorw.bat --mode module -p module=entry@default -p product=default assembleHap --no-daemon

(jbr 的 java 要前置到 PATH,因为 PackageHap 打包工具是 jar,spawn 不到 java 会报 00308018。)

从克隆到真机跑通的完整流程,我们画成了一张五步图:

构建五步图

  1. 环境确认:设备 HarmonyOS ≥ 7.0.0(26.0.0)(最低 6.1.0(135) 也能跑,但建议 7.0+),DevEco Studio + SDK 6.0.1(21)/6.0.2(22),local.properties 指向 DevEco 自带 SDK;
  2. 克隆代码:仓库已含全部大文件(33MB 静态库 + 1.7MB 头文件 + 11MB 运行时 zip),无需额外下载;
  3. DevEco 打开工程:运行配置类型必须选 OhosDebugTask(不是 OhosRunConfiguration),首次打开触发自动签名,signingConfig 从空改为 default;
  4. 编译 + Run:第一次 Run 自动签名+安装+启动,真机点 Run Test 看到 P2 SUCCESS,切到执行器页即可写代码;
  5. 后续扩展:P3 极薄宿主与 P4 Tcl/Tk 显示层仍在路线图上(见第九节)。

签名环节的三个坑,每个都值得单独记一笔:

坑现象对策
signingConfig 留空产物是 entry-default-unsigned.hap,安装报 9568320products[].signingConfig 必须填 signingConfigs 里的 name
SignHap 00303074profile 绑定了别的 bundleName签名材料按包名绑定,每个项目重新自动签名,p7b 不可跨项目复用
资源缓存改了图标/资源,构建出来还是旧的先 hvigorw --stop-daemon 再删 .hvigor 和 entry/build;只删 intermediates 子目录会报 00308018

八、开发环境与真机验收

日常开发在 DevEco Studio 里完成,ArkTS 层与 C++ 层同工程管理:

DevEco Studio 开发环境

上架版在真机上完成回归:MateBook Pro(HarmonyOS 7.0.0(26.0.0))安装启动,运行时日志出现 [runtime] reuse existing /data/storage/el2/base/haps/entry/files/ohos_python(沙箱内解压产物正确复用),Python 横幅打出 3.12.9,随后 Shell 进入 >>> 交互态。截图中 %Run 执行去重函数、变量面板展示 lru_cache 递归/迭代/生成器三种斐波那契实现,均一次通过。

需要强调的是系统版本敏感性:我们在旧版本系统(6.x)上遇到过「进程活着但 EntryAbility 不实例化」的诡异现象——无崩溃栈、无 onCreate 日志、filesDir 为空,排查了很久最后定位是设备系统版本问题,升级 HarmonyOS 7.0.0(26.0.0) 后全部恢复。所以 README 里明确建议先升级系统,并把这条排查路径(日志埋点 → 进程状态 → so 依赖 → 环境变量)写进了故障排查章节。

九、诚实的边界:已知限制与路线图

一个上架的产品,把限制说清楚和把功能做好同样重要。当前版本的限制有三条:

  1. C 扩展模块不可用。静态链接的 libpython 无法 dlopen math.so 等 lib-dynload 扩展,纯 Python 路径 100% 可用,但依赖 C 扩展的库(numpy、requests 的 ssl 层等)装不上也跑不了。根本解法是改用 shared libpython 或把常用扩展做成内建模块,这是后续大版本的事;
  2. 无 Tk 显示层。Tk 的传统后端是 X11/Win32/Aqua,鸿蒙没有原生 Tk 后端。所以上架版的 GUI 是 ArkUI 自绘的外壳,解释器本体 headless 运行;
  3. Thonny 教学功能的深度整合还在路上。当前 Shell/编辑器/变量面板覆盖了初学者的核心闭环,但上游的调试器(Thonny 最有特色的功能)、包管理器等还没有接出 UI。

路线图上还有两站:P3 极薄宿主(把 python -m thonny 的完整生命周期管理做进壳里)和 P4 显示层(评估 X11 兼容层或 Tcl/Tk 移植的可行性)。这两项涉及较大的技术选型,我们倾向先把当前 ArkUI 原生外壳这条已经验证的路走深——它性能可控、和系统集成最好,而且不受 Tk 后端缺失的制约。

十、结语

回看整个项目,最有价值的经验其实一句话就能说完:先证明真的,再做好看的。V1.0 用一个朴素的验收页锁死了「交叉编译 CPython + 上游 Thonny 源码在鸿蒙真机可跑」这个事实,之后所有的 UI 投入都建立在这个不可动摇的地基上;而 UI 升级版的每一处样式改动——clam 主题色、default_light 语法配色、%Run 仪式感、Shell 提示符——都能在上游源码里找到出处,这让「移植」二字名副其实。

对想在鸿蒙上做开源软件移植的开发者,我们的三条建议:

  1. noexec 沙箱是第一约束,先想清楚解释器/引擎以什么形态进进程(静态库链入是通用解),再动手写任何界面;
  2. 验收标准要落在内容上,exit code 和「没抛异常」说明不了任何问题,约定标记 + 逐项断言才能防住「假成功」;
  3. 限制要诚实,裁了什么、跑不了什么,写进文档比藏在 FAQ 里体面得多,也省下大量售后解释成本。

青蟒已在 HarmonyOS PC 应用市场上架,安装即用。源码、构建文档与踩坑清单随仓库开放,欢迎鸿蒙生态的开发者照着复现、提 issue、交 PR——毕竟,一条青蟒能游多远,取决于水有多宽。


上游归属与许可:Thonny © University of Tartu(MIT);CPython © Python Software Foundation(PSF License)。本项目为上游开源软件的 HarmonyOS 平台适配移植,分发时保留上游 LICENSE 与 NOTICE。

Logo

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

更多推荐