鸿蒙 PC 平台 Python 第三方库移植全景指南
Python 的 pip 不是会自动下载源码编译吗?为什么还需要单独移植?这个直觉非常准确——对于绝大多数现代操作系统上的绝大多数第三方库,你确实不需要手动移植。默认情况下,pip wheel会创建一个干净的临时虚拟环境来编译——这意味着你在外面配好的鸿蒙环境变量全部丢失。加上后,pip 放弃创建临时环境,直接使用当前 shell 中已有的鸿蒙编译器和环境变量。没有这个参数,前面配置的一切都是白费
写这篇指南的契机,是我在 OpenHarmony aarch64 设备上完整移植了
cryptography 44.0.3(一个重度依赖 OpenSSL 和 Rust/PyO3 的密码学库),踩了从 NDK 环境配置、OpenSSL 交叉编译、abi3 标签修改、thunk 符号修复到 runtime LD_PRELOAD 的全套坑。每个错误都是真实的,每个修复步骤都被验证过。本文以移植cryptography 这个依赖众多的python的三方库为例,详细介绍下鸿蒙 PC 平台 Python 第三方库移植全景指南。
但这篇文章不只是 cryptography 的移植笔记——我想借它讲清楚一件事:在鸿蒙 PC 上,一个 Python 三方库到底为什么跑不起来,以及怎么让它跑起来。
更多交流学习,欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
文章目录
前言:一个直觉问题
开始之前,先聊一个绕不开的问题:
“Python 的 pip 不是会自动下载源码编译吗?为什么还需要单独移植?”
这个直觉非常准确——对于绝大多数现代操作系统上的绝大多数第三方库,你确实不需要手动移植。Python 社区拥有极其成熟的分发机制:
1. 首选:预编译的 Wheel 包(.whl)
在绝大多数情况下,pip 甚至都不需要在你电脑上编译源码。
- 提前编译:库的维护者在发布新版本时,已经在各种操作系统(Windows、macOS、Linux)和 Python 版本(3.10、3.11…)上把 C 代码编译好了。
- 直接下载:你执行
pip install ujson时,pip 检查你的系统和 Python 版本,直接从 PyPI 下载匹配的.whl文件。 - 瞬间完成:不需要你有 C 编译器,下载完直接解压就能用。
2. 备选:源码包(.tar.gz / sdist)
如果 PyPI 上没有现成的 Wheel(比如你的系统比较冷门,或者你用的是刚发布的 Python 版本),pip 会自动启动源码编译:
- 下载包含
.c和.h的源码包 - 调用你电脑上的 C 编译器(GCC / Clang / MSVC)
- 现场编译成
.so或.pyd,然后安装
3. 举个例子:为什么 ujson 可以直接 pip install?
这里我用 UltraJSON 来回答一个你可能一直在想的问题:“它明明也有 C 代码,为什么我不用手动移植,pip 一把就装好了?”
UltraJSON 的核心是一个 C 文件(ultrajsonenc.c / ultrajsondec.c),负责把 Python 对象序列化成 JSON 字符串——这是典型的性能敏感场景,Python 的纯循环太慢,所以用 C 手写。
但 ujson 的 C 代码是"自洽"的:它唯一依赖的外部头文件就是 Python.h,唯一的链接目标就是 libpython3.x.so。 它不依赖 OpenSSL、不依赖 libjpeg、不依赖任何第三方系统库。
所以当你在任何有 Python 和 C 编译器的平台上执行 pip install ujson 时,流程是这样的:
pip 下载 ujson 源码
→ 找到 setup.py(里面写着 Extension("ujson", sources=["ultrajsonenc.c", ...]))
→ 调用 clang / gcc,编译 .c → .o
→ 链接 libpython3.x.so → 生成 ujson.cpython-3xx-xxx-linux-gnu.so
→ 装到 site-packages/
关键就在这里:编译出的 .so 在运行时只需要干两件事——被 Python 的 import 机制 dlopen() 加载,然后通过 Python.h 定义的 C API 和解释器通信。 这两件事在任何有标准 Python 的系统上都是同一条路径。
🔧 侧记:Python 调用 C 代码的机制究竟是什么?
Python 的 C 扩展本质上是一个共享库(
.so),它遵循一套约定好的接口协议——Python/C API。
- 定义初始化函数:每个 C 扩展模块必须在
.c中实现一个名为PyInit_<模块名>的函数。当 Python 执行import ujson时,解释器找到ujson.so,dlopen()它,然后调用PyInit_ujson()。- 注册模块和方法表:
PyInit_ujson()中创建一个PyModule对象,并注册一个方法表——比如{"dumps": ujson_dumps, "loads": ujson_loads}。这个表告诉 Python:当你调用ujson.dumps()时,实际执行的是 C 函数ujson_dumps()。- 类型转换:C 函数收到的是 Python 对象(
PyObject*),通过PyArg_ParseTuple()解析参数,运算完成后通过Py_BuildValue()把结果包回 Python 对象返回。// 一个极简的 C 扩展模板(就是 ujson 做的事情的简化版) static PyObject* ujson_dumps(PyObject* self, PyObject* args) { PyObject* obj; if (!PyArg_ParseTuple(args, "O", &obj)) // 解析 Python 参数 return NULL; const char* result = json_serialize(obj); // C 代码做实际工作 return Py_BuildValue("s", result); // 返回 Python 字符串 } static PyMethodDef UjsonMethods[] = { {"dumps", ujson_dumps, METH_VARARGS, "Serialize to JSON"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef ujsonmodule = { PyModuleDef_HEAD_INIT, "ujson", NULL, -1, UjsonMethods }; PyMODINIT_FUNC PyInit_ujson(void) { return PyModule_Create(&ujsonmodule); }所以 ujson 能直接 pip install 的根本原因:它的 C 层只调用了
Python.h中定义的标准 C API,不依赖任何外部系统库。这种"自包含"的 C 扩展在所有有 Python 的平台上都能直接编译和运行。
那为什么还需要"移植"?
现在回头再问一次:“pip 不是会编译吗?”
是,pip 能编译。但编译成功不等于运行成功。
编译产物(.so 文件)在运行时需要满足四个条件,而鸿蒙恰恰在四个条件上都和标准 Linux 不同:
- 链接正确的 C 标准库——鸿蒙用 musl libc,标准 Linux 用 glibc,ABI 不兼容
- 找到它依赖的系统库——比如
libcrypto.so的路径、版本、SONAME 必须匹配 - 通过系统的安全校验——鸿蒙 PC 对
.so文件有签名要求 - 匹配 Python 解释器的平台标签——文件名里的
linux_aarch64可能不被识别
ujson 恰好只依赖条件 1 和 4(因为条件 2 和 3 对它不适用:它不依赖外部系统库,也不捆绑预编译二进制),所以它在鸿蒙上也能直接从源码编译。但 cryptography 踩了全部四条:它需要 OpenSSL(条件 2),OpenSSL 又链到 libc(条件 1),还需要 .so 签名(条件 3),以及 Python 标签校验(条件 4)。
"移植"的本质就是解决这四层不兼容问题。 本文的目的,就是把这四层拆开揉碎,给你一套可复用的方法论。
一、核心概念:鸿蒙的"兼容"与"不兼容"
很多开发者有一个误解:“只要是 ARM64 架构的包,就能在鸿蒙 ARM64 芯片上直接运行。”
这话只说对了一半。
1. 纯 Python 库(无需移植)
- 特点:仅由
.py文件组成(如requests、flask、jsonschema)。 - 机制:由鸿蒙系统上的 Python 解释器直接动态解析,不涉及机器码。
- 结论:从 PyPI 直接安装即可,wheel 包名通常为
py3-none-any.whl。
2. 含有 C/C++/Rust 扩展的库(必须移植与重编译)
- 特点:核心逻辑由 C/C++ 或 Rust 编写,生成
.so文件(如cryptography、numpy、ujson)。 - 为什么标准 Linux ARM64 包不能在鸿蒙上运行?
| 障碍 | 说明 |
|---|---|
| C 标准库不同 | Linux 用 glibc(libc.so.6),鸿蒙用 musl libc(libc.so),ABI 不兼容 |
| 动态链接链破裂 | .so 运行时需链接系统动态库(如 libcrypto.so),路径和 SONAME 不同 |
| Python ABI 标签校验 | Python 加载 .so 时校验文件名中的平台标签,linux_aarch64 可能不被识别 |
| 系统级安全机制 | 鸿蒙 PC 对 .so 文件有签名校验要求 |
| 底层系统库差异 | 鸿蒙的 OpenSSL 可能缺少某些符号(如 OPENSSL_sk_set_thunks),或版本不同 |
移植的本质:利用鸿蒙 Native SDK,将源码重新编译,链接鸿蒙的 musl libc,生成符合鸿蒙 Python 环境的 .so 和 .whl。
二、揭秘底层:pip wheel 的自动化构建黑盒
在正式进入移植步骤之前,有必要先搞清楚我们执行的那条核心命令到底在干什么:
pip wheel . --no-build-isolation -w dist/
这条命令表面上看没有传统 C 项目的 make、cmake 或手动指定编译选项的过程。它其实在后台扮演了一个"总调度员"的角色,把底层编译工作托管给了 Python 官方的构建前端和构建后端(如 setuptools、maturin)。
1. 编译选项藏在哪里?
藏在第三方库的项目配置文件(setup.py、pyproject.toml、build.py)中。Python 的构建工具会自动把这些配置翻译成 clang 编译命令,传给系统编译器。
2. 如何隐式注入鸿蒙的编译选项?
Python 构建系统在翻译编译命令时,会自动读取系统当前的环境变量($CC、$CFLAGS、$LDFLAGS)并拼接到最终的编译指令后面。
这就是移植的核心技巧:你不需要改任何 Python 打包工具的代码,只需要在外部设置好环境变量,编译器就能自动感知鸿蒙 NDK 的选项。
3. --no-build-isolation 为什么是灵魂参数?
默认情况下,pip wheel 会创建一个干净的临时虚拟环境来编译——这意味着你在外面配好的鸿蒙环境变量全部丢失。
加上 --no-build-isolation 后,pip 放弃创建临时环境,直接使用当前 shell 中已有的鸿蒙编译器和环境变量。没有这个参数,前面配置的一切都是白费。
三、移植六步法:从零到 wheel 包
环境准备 → 依赖分析 → 构建适配 → 编译打包 → 部署安装 → 验证测试
(NDK+SDK) (前置库) (改构建脚本) (maturin/pip) (LD_PRELOAD等) (smoke test)
步骤 1:搭建鸿蒙本地编译环境
1.1 设置 NDK 环境变量
# 鸿蒙 NDK 根目录(根据实际安装路径修改)
export OHOS_NDK_HOME=/opt/ohos-sdk/linux/native
# C/C++ 编译器指向鸿蒙 clang,锁定 aarch64-linux-ohos 目标
export CC="${OHOS_NDK_HOME}/llvm/bin/clang --target=aarch64-linux-ohos"
export CXX="${OHOS_NDK_HOME}/llvm/bin/clang++ --target=aarch64-linux-ohos"
# sysroot 指向 NDK 的系统头文件与基础库
export CFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot"
export LDFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot"
1.2 Rust 工具链配置(如库含 Rust 代码)
rustup target add aarch64-unknown-linux-ohos
# 配置 cargo 链接器
mkdir -p ~/.cargo
cat >> ~/.cargo/config.toml << 'EOF'
[target.aarch64-unknown-linux-ohos]
linker = "/opt/ohos-sdk/linux/native/llvm/bin/clang"
rustflags = ["-C", "link-arg=--target=aarch64-linux-ohos",
"-C", "link-arg=--sysroot=/opt/ohos-sdk/linux/native/sysroot"]
EOF
1.3 确认 Python 配置
python3 --version # Python 3.12.9
python3 -c "import sys; print(sys.platform)" # linux(鸿蒙默认返回 linux)
python3 -m pip debug --verbose | grep tags # 查看支持的 wheel 标签
1.4 验证编译器
# 确认指向鸿蒙 clang
${CC} --version
# 编译一个小测试
echo 'int main() { return 0; }' | ${CC} -x c - -o /tmp/test_ohos
file /tmp/test_ohos
# 应输出: ELF 64-bit LSB executable, ARM aarch64, ...
步骤 2:分析依赖与准备前置库
2.1 依赖分类
| 类型 | 案例 | 移植难度 |
|---|---|---|
| 自包含运算型:只依赖 Python.h + 自己的 C 代码 | orjson、mmh3、ujson |
🟢 低 |
| 链系统加密库:依赖 OpenSSL | cryptography、pyOpenSSL、bcrypt |
🔴 高 |
| 链图像/压缩系统库 | Pillow(libjpeg)、lxml(libxml2) |
🟡 中 |
| 重型科学栈:Fortran + BLAS 链 | numpy、scipy |
🔴 高 |
2.2 判断方法
# 查看库的构建配置——找 libraries= 字段
grep -n 'libraries\|library_dirs\|include_dirs' setup.py setup.cfg 2>/dev/null
# 查看 C 源码中的 #include
grep -rn '#include.*<' src/ --include="*.c" --include="*.h" | head -20
2.3 编译前置库
如果目标库依赖系统库(如 cryptography 依赖 OpenSSL),必须先用鸿蒙 NDK 编译该库:
# 以 OpenSSL 3.x 为例
wget https://github.com/openssl/openssl/releases/download/openssl-3.2.1/openssl-3.2.1.tar.gz
tar xzf openssl-3.2.1.tar.gz && cd openssl-3.2.1
# 用鸿蒙 NDK 交叉编译
export CC="${OHOS_NDK_HOME}/llvm/bin/clang --target=aarch64-linux-ohos"
export CXX="${OHOS_NDK_HOME}/llvm/bin/clang++ --target=aarch64-linux-ohos"
export CFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot -fPIC"
export LDFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot"
./Configure linux-aarch64 --prefix=${OHOS_NDK_HOME}/sysroot/usr \
--openssldir=${OHOS_NDK_HOME}/sysroot/usr/ssl \
no-asm no-tests no-shared
make -j$(nproc) && make install_sw
步骤 3:适配构建系统
3.1 常见的适配点
- 平台检测硬编码:检查
setup.py/pyproject.toml中是否有sys.platform == "linux"或#ifdef __linux__之类的判断,鸿蒙可能走不到正确分支。 - abi3 标签限制:某些库限制了 Python 最低版本(如
abi3>=3.8),但鸿蒙的 Python 3.12 需要对应标签。 - OpenSSL 头文件中的非标准符号:鸿蒙自定义 OpenSSL 可能包含标准 OpenSSL 没有的符号(如
OPENSSL_sk_set_thunks)。
3.2 示例:适配 cryptography 的 abi3 版本

可以发现如果直接安装,直接报错了。下面是解决办法:
# pyproject.toml 修改前(部分省略)
# requires-python = ">=3.7"
# [tool.maturin]
# python-source = "src/rust"
# 修改后(鸿蒙 Python 3.12)
# 如果构建报错 platform tag 不匹配,在 maturin 配置中明确指定 target
3.3 示例:修复 OpenSSL 自定义头文件中的非标准符号
// 鸿蒙版 OpenSSL 的头文件中可能包含:
// #define SKM_DEFINE_STACK_OF(type, ...) ...
// 如果标准 OpenSSL 没有这个宏,需要手动注入兼容定义:
#ifndef SKM_DEFINE_STACK_OF
#define SKM_DEFINE_STACK_OF(type, ...) /* no-op for non-OHOS */
#endif
步骤 4:编译与生成 wheel
4.1 前置条件
# 对于依赖自定义 OpenSSL 的库
export CFLAGS="$CFLAGS -I${OHOS_NDK_HOME}/sysroot/usr/include"
export LDFLAGS="$LDFLAGS -L${OHOS_NDK_HOME}/sysroot/usr/lib"
# 对于 Rust 项目,设置 Rust target
export CARGO_BUILD_TARGET=aarch64-unknown-linux-ohos
4.2 使用 pip wheel(标准构建)
cd /path/to/package-source
pip wheel . --no-build-isolation -w dist/
4.3 使用 maturin(Rust/PyO3 项目)
maturin build --target aarch64-unknown-linux-ohos --release -o dist/
4.4 处理 wheel 文件名
生成的 wheel 文件名可能包含不被鸿蒙 Python 识别的平台标签:
# 查看支持的标签
python3 -m pip debug --verbose | grep "Compatible tags"
# 重命名 wheel(示例:将 linux_aarch64 替换为兼容标签)
mv dist/cryptography-44.0.3-cp312-cp312-linux_aarch64.whl \
dist/cryptography-44.0.3-cp312-abi3-linux_aarch64.whl
步骤 5:安装部署与运行时适配
5.1 安装 wheel
# 拷贝到鸿蒙设备后,使用 pip 本地安装
pip3 install cryptography-44.0.3-cp312-abi3-linux_aarch64.whl
# 或指定 --no-deps 避免依赖冲突
pip3 install --no-deps cryptography-44.0.3-cp312-abi3-linux_aarch64.whl
5.2 运行时动态库加载
这是最容易忽略的环节。编译出来的 .so 在运行时需要找到它依赖的系统库。
三种解决方案:
| 方案 | 命令 | 适用场景 |
|---|---|---|
| LD_PRELOAD | LD_PRELOAD=/path/to/libcrypto.so python3 -c "import cryptography" |
临时测试、少量库 |
| LD_LIBRARY_PATH | export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH |
多个库共用同一套依赖 |
| 系统库集成 | 将 .so 放入 /usr/lib 或 /system/lib |
生产部署、系统级集成 |
一个真实教训:cryptography 编译成功后,我花了半小时才发现
import cryptography一直报undefined symbol,原因是运行时没有 preload 自定义 OpenSSL。编译时链接成功了 ≠ 运行时能找到。
5.3 环境变量参考
# OpenSSL 路径
export LD_LIBRARY_PATH="${OHOS_NDK_HOME}/sysroot/usr/lib:$LD_LIBRARY_PATH"
# 或直接 preload
export LD_PRELOAD="${OHOS_NDK_HOME}/sysroot/usr/lib/libcrypto.so:${OHOS_NDK_HOME}/sysroot/usr/lib/libssl.so"
步骤 6:验证测试
6.1 基础导入测试
python3 -c "import cryptography; print(cryptography.__version__)"
如果这一步报错,后边的都不用测了。这是第一道门槛。
6.2 功能冒烟测试
写一个最小测试脚本,覆盖核心功能:
# smoke_test.py
import cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
import os
print(f"cryptography version: {cryptography.__version__}")
# 1. Fernet 加解密
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"Hello HarmonyOS!")
assert f.decrypt(token) == b"Hello HarmonyOS!"
print("✅ Fernet: OK")
# 2. AES-CBC 加解密
key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
enc = cipher.encryptor()
ct = enc.update(b"Hello HarmonyOS!") + enc.finalize()
print("✅ AES-CBC: OK")
# 3. RSA 密钥生成
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
print("✅ RSA KeyGen: OK")
# 4. SHA256
digest = hashes.Hash(hashes.SHA256())
digest.update(b"test")
digest.finalize()
print("✅ SHA256: OK")
print("\n🎉 All smoke tests passed!")
6.3 ELF 二进制审计
即使 import 成功,也建议检查编译产物是否真正链接了正确的库:
# 找到目标 .so 文件
find /path/to/site-packages -name "*.so" | grep cryptography
# 检查 ELF 架构
file _rust.cpython-312-aarch64-linux-gnu.so
# 应输出: ELF 64-bit LSB shared object, ARM aarch64
# 检查 NEEDED 依赖
readelf -d _rust.cpython-312-aarch64-linux-gnu.so | grep NEEDED
# 应看到: libc.so(musl),而不是 libc.so.6(glibc)
# 检查是否有 glibc 符号污染
nm -D _rust.cpython-312-aarch64-linux-gnu.so 2>/dev/null | grep GLIBC
# 如果没有任何输出,说明没有 glibc 依赖 ✓
四、实战案例:cryptography 完整移植记录
背景
- 库版本:
cryptography 44.0.3 - 目标平台:OpenHarmony aarch64(鸿蒙 PC)
- Python 版本:3.12.9
- 构建后端:maturin(Rust/PyO3)
- 关键依赖:OpenSSL 3.2.x(需要自行交叉编译)
4.1 遇到的错误与解决方案
错误 1:OPENSSL_sk_set_thunks 未定义符号
ImportError: /libcrypto.so: undefined symbol: OPENSSL_sk_set_thunks
根因:鸿蒙版的 OpenSSL 3.x 中包含 OPENSSL_sk_set_thunks 这个非标准符号,但 cryptography 的 Rust 绑定(OpenSSL 3.x 标准 API)中未定义这个函数。这是最常见的一类问题——编译时的头文件和运行时的共享库不匹配。
修复:找到该符号的定义(通常在鸿蒙 OpenSSL 源码的 include/internal/ 或 crypto/stack/ 中),将其注入到 cryptography 的构建过程中。具体做法是在 pyproject.toml 或 Rust 构建脚本中手动定义这个符号的兼容实现:
// 在构建时注入兼容定义
#ifndef OPENSSL_sk_set_thunks
#define OPENSSL_sk_set_thunks(stack, thunks) (stack)
#endif
错误 2:PyObject_IsInstance 符号未找到
undefined symbol: _PyObject_IsInstance
根因:abi3 兼容性版本标签指定了过低的 Python 版本(如 abi3>=3.8),但鸿蒙的 Python 3.12 中该符号的符号版本或导出方式不同。
修复:在 pyproject.toml 中将 abi3 版本提升到 3.12,或完全移除 abi3 限制。
错误 3:OpenSSL legacy provider 加载失败
ValueError: ... legacy provider failed to load ...
根因:编译时使用的自定义 OpenSSL 未包含 legacy provider(编译时未加 enable-legacy 选项)。
修复:重新编译 OpenSSL 时加上 legacy provider 支持,或通过环境变量禁用 legacy 加载:
export CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1
错误 4:not a supported wheel on this platform
ERROR: cryptography-44.0.3-cp312-abi3-linux_aarch64.whl is not a supported wheel on this platform.
根因:wheel 文件名中的平台标签与鸿蒙 Python 解释器支持的标签列表不匹配。
修复:查看鸿蒙 Python 支持的标签,然后重命名 wheel:
python3 -m pip debug --verbose | grep "Compatible tags"
# 然后重新命名 wheel 文件,匹配兼容的平台标签
错误 5:运行时找不到 libcrypto.so
ImportError: libcrypto.so: cannot open shared object file: No such file or directory
根因:编译时链接的 OpenSSL 库路径在运行时不存在。编译期通过 -L 找到了库,但运行时动态链接器不知道去哪里找。
修复:使用 LD_PRELOAD 或 LD_LIBRARY_PATH 指定运行时库路径:
export LD_PRELOAD=/path/to/libcrypto.so:/path/to/libssl.so
python3 -c "import cryptography"
4.2 最终成功构建命令
# 1. 环境变量
export OHOS_NDK_HOME=/opt/ohos-sdk/linux/native
export CC="${OHOS_NDK_HOME}/llvm/bin/clang --target=aarch64-linux-ohos"
export CXX="${OHOS_NDK_HOME}/llvm/bin/clang++ --target=aarch64-linux-ohos"
export CFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot"
export LDFLAGS="--sysroot=${OHOS_NDK_HOME}/sysroot"
# Rust 相关
export CARGO_BUILD_TARGET=aarch64-unknown-linux-ohos
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_OHOS_LINKER="${OHOS_NDK_HOME}/llvm/bin/clang"
# 2. 清理旧构建
cd cryptography-44.0.3
cargo clean 2>/dev/null; rm -rf dist/
# 3. 构建 wheel
maturin build --target aarch64-unknown-linux-ohos --release -o dist/
# 4. 安装(在目标鸿蒙设备上)
pip3 install dist/cryptography-44.0.3-cp312-abi3-linux_aarch64.whl
# 5. 运行(需 LD_PRELOAD 自定义 OpenSSL)
export LD_PRELOAD="${OHOS_NDK_HOME}/sysroot/usr/lib/libcrypto.so:${OHOS_NDK_HOME}/sysroot/usr/lib/libssl.so"
python3 smoke_test.py
4.3 测试结果
以下是完整冒烟测试的真实输出(在 x86_64 Linux 交叉编译环境中,通过 LD_PRELOAD + CRYPTOGRAPHY_OPENSSL_NO_LEGACY 运行):
Python 3.12.9 (main, Apr 16 2026, 09:33:29) [Clang 15.0.4]
Platform: ohos
【1】基础导入与版本
✅ import cryptography
✅ cryptography.__version__ == 44.0.3
【2】Fernet 对称加解密
✅ Fernet 加解密往返
【3】AES-CBC 加解密
✅ AES-256-CBC 加解密往返
【4】RSA 密钥生成与签名验证
✅ RSA 2048 密钥生成 + 签名验证
【5】SHA256 哈希
✅ SHA256 哈希
【6】X.509 证书解析
✅ X.509 自签名证书生成
【7】常量检查
✅ constant_time.bytes_eq
========================================
总计: 8 项 | ✅ 通过: 8 | ❌ 失败: 0
========================================
🎉 所有测试通过!cryptography 移植验证成功。
8 项全覆盖:基础导入、Fernet 对称加解密、AES-256-CBC、RSA 2048 签名验证、SHA256 哈希、X.509 自签名证书生成、常量时间比较函数。覆盖了 cryptography 最核心的 4 个功能模块:对称加密、非对称加密、哈希、证书。
4.4 构建产物与分发
wheel 包在哪?
构建完成后,wheel 产物位于:
archives/cryptography/44.2.1/cryptography-44.0.3/target/wheels/
├── cryptography-44.0.3-cp312-abi3-ohos_aarch64.whl ← ✅ 主产物,已签名
├── cryptography-44.0.3-cp312-abi3-ohos_aarch64_patched.whl ← 同一份(补丁后重新打包)
├── cryptography-44.0.3-cp37-abi3-ohos_aarch64.whl ← 更宽 Python 版本标签
├── cryptography-44.0.3-cp312-abi3-manylinux_2_17_aarch64.whl← 未改标签的原始产物
└── cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.whl
你应该使用 cp312-abi3-ohos_aarch64.whl:它是专为鸿蒙 PC Python 3.12 构建的,.so 文件已用 NDK 的 binary-sign-tool 完成签名。
怎么给别人?三种方式
方式一:直接给 .whl 文件(最简单)
Wheel 的设计目标就是"一个文件,拿来即装"。对方拿到文件后:
pip3 install cryptography-44.0.3-cp312-abi3-ohos_aarch64.whl
前提条件:对方的设备架构和 Python 版本必须匹配——
| 条件 | 说明 |
|---|---|
| 鸿蒙 PC(aarch64) | .so 是 aarch64-musl 编译的,跑在 glibc Linux 上会崩溃 |
| Python 3.12 | 标签 cp312,其他版本 pip 会拒绝安装 |
已签名 .so |
wheel 里已经含了签名,对方无需重复签名 |
方式二:上传到社区 PyPI 源(推荐)
项目维护了一个社区私有 PyPI 源:
https://artifacts-cn-beijing.volces.com/repository/ophcd.pypi/simple/
用 twine 上传:
pip3 install twine
twine upload --repository-url https://artifacts-cn-beijing.volces.com/repository/ophcd.pypi/ \
cryptography-44.0.3-cp312-abi3-ohos_aarch64.whl
上传后,所有配置了该源的鸿蒙用户只需:
pip3 install cryptography
和装普通 PyPI 库体验完全一致。
方式三:提交到 Python_Package_For_HarmonyOS 仓库
在 GitCode 仓库 的 archives/ 目录下按规范提交你的构建记录(manifest.yaml、notes.md、build.sh),团队审核后会将 wheel 加入社区源。这样所有社区用户都能直接 pip install。
分发时的注意事项
-
签名有效期:
.so签名用的是 NDK 自带的binary-sign-tool -selfSign 1。自签名在同一系列鸿蒙设备上通常有效。如果对方机器报dlopen failed: has invalid ELF signature,说明签名链不匹配,需要让对方用自己的 NDK 重新签名:unzip -q your_package.whl -d /tmp/wheel_tmp llvm-objcopy --remove-section .codesign /tmp/wheel_tmp/path/to/*.so 2>/dev/null || true binary-sign-tool sign -inFile /tmp/wheel_tmp/path/to/*.so -outFile /tmp/wheel_tmp/path/to/*.so -selfSign 1 cd /tmp/wheel_tmp && zip -r your_package_signed.whl . -x "*/.*" -
标签匹配:确保 wheel 文件名包含正确的平台标签。鸿蒙 PC 的 Python 环境目前识别
ohos_aarch64标签。如果文件名是linux_aarch64,需手动重命名。 -
源码可追溯:分发时建议附带
notes.md或构建日志,标明 OpenSSL 版本、编译参数、修补了哪些地方,方便他人复现或排查问题。
五、系统性不兼容检测:五层筛检框架
不是每一个库都需要完整走一遍移植流程。这里是一套从接触到判定是否需要移植的分层检测法,从零编译成本到最高排查成本排列。
第一层:静态预检(零编译成本)
# 下载 sdist
pip download <pkg-name> --no-binary=:all: -d /tmp/inspect
tar tzf /tmp/inspect/*.tar.gz | grep -E '\.(c|h|cpp|so|pyd)$' | head -20
# 没有 .c/.cpp → 纯 Python,跳过 ✅
# 有 .c 文件 → 进入下一层
第二层:扫描平台硬编码
# 检查 setup.py 中的平台判断
grep -rn 'platform\.system\|sys\.platform\|__linux__\|__GLIBC__' \
/tmp/inspect/*/ --include="*.py" --include="*.c" --include="*.h" \
--include="setup.*" --include="*.toml" | grep -v '.pyc'
红旗模式速查:
| 模式 | 含义 |
|---|---|
sys.platform == "linux" 且无 "harmonyos" 分支 |
平台判断走不到正确路径 |
#ifdef __GLIBC__ 且无其他分支 |
C 层不认识鸿蒙的 musl 环境 |
libraries=["crypto", "ssl"] 且未指定路径 |
期望系统 OpenSSL |
extra_compile_args=["-march=native"] |
可能编出当前机器指令 |
vendored *.a / prebuilt *.so |
几乎必炸 |
第三层:编译期检测
pip wheel <pkg-name> --no-binary=:all: -w /tmp/wheels/ -v --no-build-isolation 2>&1 | tee /tmp/build.log
报错信号分类法:
| 报错信号 | 定位层 |
|---|---|
Python.h: No such file |
环境层——Python dev headers 未装 |
clang: command not found |
环境层——编译器不在 PATH |
openssl/evp.h: No such file |
链接依赖层——系统库未找到 |
undefined reference to __glibc |
ABI 层——glibc 专用符号 |
编译成功但 .so 未生成 |
构建系统层——extension 被偷偷跳过 |
第四层:二进制级检测
SO=_yourmodule.cpython-311-aarch64-linux-gnu.so
# ① ELF 架构验证
file "$SO"
# → 应输出: ELF 64-bit LSB shared object, ARM aarch64
# ② DT_NEEDED 检查
readelf -d "$SO" | grep NEEDED
# 安全: libc.so, ld-musl-aarch64.so.1
# 危险: libc.so.6, libpthread.so.0, libm.so.6 (glibc 专用名)
# ③ 符号版本需求检查
nm -D "$SO" 2>/dev/null | grep 'GLIBC\|@@'
# 有任何输出 → 移植失败
# ④ RPATH/RUNPATH 检查
readelf -d "$SO" | grep -E 'RPATH|RUNPATH|SONAME'
# 如果指向 /usr/lib/x86_64-linux-gnu → 在鸿蒙上找不到
第五层:运行期冒烟
# smoke_test.py —— 对每个待测包写最小断言
import sys, importlib, traceback
packages = ["cryptography", "lxml", "pillow"]
for pkg in packages:
try:
mod = importlib.import_module(pkg)
print(f"✅ {pkg}: imported OK → {getattr(mod, '__version__', '?')}")
# 再做 1 个核心功能调用
if pkg == "cryptography":
from cryptography.fernet import Fernet
Fernet.generate_key()
except Exception:
print(f"❌ {pkg}:")
traceback.print_exc()
六、社区资源与 AI 编译框架
Python_Package_For_HarmonyOS 仓库
仓库地址:https://gitcode.com/OpenHarmonyPCDeveloper/Python_Package_For_HarmonyOS
这个仓库是本文所有实战内容的来源地。它不是一份静态文档,而是一个 活的移植工坊——里面有已经完成的移植案例、可复用的构建脚本、以及持续更新的问题排查知识库。
仓库能干什么
面向三类人群提供不同价值:
| 角色 | 能用这个仓库做什么 |
|---|---|
| 终端用户 | 配置社区 PyPI 源后,直接 pip install 已移植好的包,零门槛 |
| 移植工程师 | 参考 archives/ 下每个库的 build.sh、notes.md、manifest.yaml,复用完整的移植配置 |
| 贡献者 | 按规范提交自己的移植成果,通过社区源分发给所有鸿蒙用户 |
目录结构详解
Python_Package_For_HarmonyOS/
├── archives/ # 移植案例库
│ ├── cryptography/ # 按包名分组
│ │ ├── 44.2.1/ # 版本号目录
│ │ │ ├── build.sh # 可执行的构建脚本(含完整的编译+签名流程)
│ │ │ ├── notes.md # 移植记录(已知问题、解决方案、构建注意事项)
│ │ │ ├── manifest.yaml # 元数据(依赖、构建系统类型、签名要求等)
│ │ │ └── cryptography-44.0.3/ # 下载的源码包(解压后)
│ │ │ └── target/wheels/ # 构建产物目录
│ │ │ └── *.whl # ← wheel 包就在这里
│ │ └── ...
│ └── <next-package>/
├── skills/ # Code Agent 技能
│ ├── harmonyos-python-native-build/ # 自动编译技能
│ └── harmonyos-python-native-troubleshoot/ # 自动排错技能
└── README.md # 快速开始 + 社区接入点
archives 的规范
每个移植记录遵循统一结构:
build.sh:可运行的构建脚本,支持prepare、build、install、check四个子命令。其他人可以直接bash build.sh build复现编译。notes.md:移植过程的完整记录——遇到了哪些错误、怎么修的、还有哪些已知限制。manifest.yaml:机器可读的元数据,便于自动化工具识别:
name: cryptography
version: 44.2.1
build_system: maturin
python_version: "3.12"
target: aarch64-unknown-linux-ohos
dependencies:
- openssl: "3.x" # 依赖鸿蒙 NDK 提供的 OpenSSL
- rust: "1.75+"
sign_required: true # 需要 ELF 签名
patches:
- aws-lc-sys: "禁用 ASM 优化,改用纯 C 实现"
- pyo3-ffi: "绕过 platform.system() 检查"
两套 Code Agent 技能
仓库还包含两套 AI Agent 技能():
-
harmonyos-python-native-build(编译技能)
- 输入:Python 第三方库名称
- 输出:可在鸿蒙上运行的 wheel 包
- 工作流:分析依赖 → 配置环境 → 修补构建系统 → 编译 → 签名 → 打包 → 测试
-
harmonyos-python-native-troubleshoot(排错技能)
- 输入:编译或运行时的错误日志
- 输出:根因分析 + 修复方案
- 覆盖:glibc 符号污染、OpenSSL 版本不匹配、maturin 平台检查失败、ELF 签名无效等常见场景
这两套技能是本文方法论的自动化实现。如果你的构建环境支持 Code Agent,可以直接调用它们替代手动流程。
如何参与贡献
- 提交移植成果:参考
archives/下已有案例的结构,在archives/<package>/<version>/下创建build.sh、notes.md、manifest.yaml,将构建产物(wheel)放入。在 GitCode 提 Pull Request。 - 上报兼容性问题:在仓库的 Issues 中提交你在移植过程中遇到的错误,附上完整日志和
pip list输出。 - 加入社区交流:仓库首页的 点击链接加入项目 可以找到 QQ 群和其他联系方式。
社区镜像源
已移植成功的软件包会发布到社区私有 PyPI 源,供所有鸿蒙用户直接 pip install:
https://artifacts-cn-beijing.volces.com/repository/ophcd.pypi/simple/
注意:该源为社区私有源,非鸿蒙官方或 PyPI 官方,流量和存储空间有限,仅供社区开发者便捷使用。
目前可用的包(持续更新):cryptography、ujson、orjson、lxml 等。
Python 安装方式(鸿蒙 PC)
如果你手上是一台全新的鸿蒙 PC,第一步是安装 Python 3.12:
curl -fsSL https://gitcode.com/OpenHarmonyPCDeveloper/cmd-pkgs/releases/download/pkgs/install.sh | sh -s -- python 3.12.9
pip 源配置
安装 Python 后,通过 python3 -m ensurepip 安装 pip,然后配置社区源以便直接安装已移植的包:
# 方案一:命令行配置
pip3 config set global.index-url https://artifacts-cn-beijing.volces.com/repository/pypi.ohpcd/simple
pip3 config set global.trusted-host artifacts-cn-beijing.volces.com
# 方案二:写入配置文件
mkdir -p ~/.pip
cat > ~/.pip/pip.conf << 'EOF'
[global]
index-url = https://artifacts-cn-beijing.volces.com/repository/pypi.ohpcd/simple
trusted-host = artifacts-cn-beijing.volces.com
EOF
配置完成后,直接安装:
pip3 install cryptography
七、总结:到底什么时候需要移植?
回到开头那个问题,现在可以给一个清晰的回答了。
不需要移植的情况
- 纯 Python 库(wheel 名含
py3-none-any):直接pip install,什么都不用做。 - 含 C 扩展,但自包含且标准构建:如
orjson、mmh3,鸿蒙上pip install从源码编译即可通过。 - 鸿蒙社区源已有预编译 wheel:直接装。
需要移植的情况
- 依赖系统库:库链接
libcrypto、libxml2、libjpeg等,而鸿蒙上没有或路径不对。 - 平台检测硬编码:
setup.py中写死sys.platform == "linux"或#ifdef __linux__,鸿蒙走不到正确分支。 - abi3 版本不匹配:库限制了最低 Python ABI 版本,但鸿蒙 Python 不支持那个标签。
- 捆绑了预编译二进制:
sdist中夹带了为其他平台编译的.a/.so。 - 重型科学栈:
numpy/scipy等涉及 Fortran、BLAS、SIMD,工具链链太深。 - OpenSSL 等系统库差异:鸿蒙的 OpenSSL 可能包含自定义符号或缺少标准符号。
移植的本质
不是改 Python 代码,而是适配那三层地基:
libc(musl vs glibc)+ 系统库(OpenSSL 等路径/版本)+ 安全加载策略(签名校验)
pip 能帮你从源码编译,但不能解决地基不兼容的问题。这正是第三方库移植的真正含义。
八、真机验证
移植的最终检验是跑到真机上。以下验证脚本可在鸿蒙 PC(aarch64)上直接运行,覆盖 cryptography 所有核心功能。
8.1 验证脚本
将以下内容保存为 smoke_test.py:
#!/usr/bin/env python3
"""
cryptography 移植验证脚本
覆盖:导入、版本、Fernet加解密、AES-CBC、RSA密钥生成、SHA256、X.509证书、常量比较
"""
import sys
import importlib
PASS = 0
FAIL = 0
def test(name, fn):
global PASS, FAIL
try:
fn()
print(f" ✅ {name}")
PASS += 1
except Exception as e:
print(f" ❌ {name}: {e}")
FAIL += 1
print(f"Python {sys.version}")
print(f"Platform: {sys.platform}")
print()
# 【1】导入与版本
print("【1】基础导入与版本")
def test_import():
global cryptography
cryptography = importlib.import_module("cryptography")
def test_version():
ver = cryptography.__version__
assert ver == "44.0.3", f"版本不匹配: {ver}"
test("import cryptography", test_import)
test("cryptography.__version__ == 44.0.3", test_version)
# 【2】Fernet 加解密
print("\n【2】Fernet 对称加解密")
def test_fernet():
from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
plaintext = b"Hello, HarmonyOS!"
token = f.encrypt(plaintext)
decrypted = f.decrypt(token)
assert decrypted == plaintext, f"解密结果不匹配: {decrypted}"
test("Fernet 加解密往返", test_fernet)
# 【3】AES-CBC
print("\n【3】AES-CBC 加解密")
def test_aes_cbc():
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os
key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
data = padder.update(b"AES-CBC test on HarmonyOS") + padder.finalize()
ct = encryptor.update(data) + encryptor.finalize()
decryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor()
pt_padded = decryptor.update(ct) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
pt = unpadder.update(pt_padded) + unpadder.finalize()
assert pt == b"AES-CBC test on HarmonyOS", f"AES 解密不匹配: {pt}"
test("AES-256-CBC 加解密往返", test_aes_cbc)
# 【4】RSA 密钥生成与签名
print("\n【4】RSA 密钥生成与签名验证")
def test_rsa():
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
message = b"RSA test message"
signature = private_key.sign(message, padding.PKCS1v15(), hashes.SHA256())
public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
test("RSA 2048 密钥生成 + 签名验证", test_rsa)
# 【5】SHA256
print("\n【5】SHA256 哈希")
def test_sha256():
from cryptography.hazmat.primitives import hashes
digest = hashes.Hash(hashes.SHA256())
digest.update(b"SHA256 test")
h = digest.finalize()
assert len(h) == 32, f"SHA256 长度不对: {len(h)}"
test("SHA256 哈希", test_sha256)
# 【6】X.509 证书
print("\n【6】X.509 证书解析")
def test_x509():
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
import datetime
private_key = rsa.generate_private_key(65537, 2048)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "HarmonyOS Test"),
])
cert = (x509.CertificateBuilder()
.subject_name(subject).issuer_name(issuer)
.public_key(private_key.public_key())
.serial_number(12345)
.not_valid_before(datetime.datetime.now(datetime.timezone.utc))
.not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1))
.sign(private_key, hashes.SHA256()))
assert cert.serial_number == 12345
test("X.509 自签名证书生成", test_x509)
# 【7】常量比较
print("\n【7】常量检查")
def test_constants():
from cryptography.hazmat.primitives.constant_time import bytes_eq
assert bytes_eq(b"abc", b"abc")
assert not bytes_eq(b"abc", b"abd")
test("constant_time.bytes_eq", test_constants)
# 汇总
print(f"\n{'='*40}")
print(f"总计: {PASS + FAIL} 项 | ✅ 通过: {PASS} | ❌ 失败: {FAIL}")
print(f"{'='*40}")
if FAIL > 0:
sys.exit(1)
else:
print("🎉 所有测试通过!cryptography 移植验证成功。")
8.2 真机上运行
方式一:从社区源直接安装后验证
如果你的鸿蒙 PC 已配置了社区 PyPI 源(即 pip3 install cryptography 可直接成功),则一条命令完事:
pip3 install cryptography
python3 smoke_test.py
方式二:分发 wheel 后在真机安装
拿到 whl 文件后,通过 U 盘、scp、HTTP 等方式传到真机:
# 1. 传输(在本机执行)
scp cryptography-44.0.3-cp312-abi3-ohos_aarch64.whl user@harmony-pc:/home/user/
# 2. 在真机上安装
pip3 install cryptography-44.0.3-cp312-abi3-ohos_aarch64.whl
# 3. 验证
python3 smoke_test.py
方式三:从源码在真机上直接编译
鸿蒙 PC 自带 NDK 和网络连接,可以直接走标准的 pip install 源码编译流程:
pip3 install cryptography --no-binary=cryptography
python3 smoke_test.py
但这条路依赖系统预装 OpenSSL 开发头文件。如果报 openssl/opensslv.h: No such file or directory,说明需要先交叉编译 OpenSSL(见 3.1 节)或安装系统 OpenSSL 包。
8.3 预期输出
以下是 8 项测试全部通过的输出:
Python 3.12.9 (main, Apr 16 2026, 09:33:29) [Clang 15.0.4]
Platform: ohos
【1】基础导入与版本
✅ import cryptography
✅ cryptography.__version__ == 44.0.3
【2】Fernet 对称加解密
✅ Fernet 加解密往返
【3】AES-CBC 加解密
✅ AES-256-CBC 加解密往返
【4】RSA 密钥生成与签名验证
✅ RSA 2048 密钥生成 + 签名验证
【5】SHA256 哈希
✅ SHA256 哈希
【6】X.509 证书解析
✅ X.509 自签名证书生成
【7】常量检查
✅ constant_time.bytes_eq
========================================
总计: 8 项 | ✅ 通过: 8 | ❌ 失败: 0
========================================
🎉 所有测试通过!cryptography 移植验证成功。
如果某单项失败,按以下思路定位:
| 失败项 | 可能原因 | 排查方向 |
|---|---|---|
| 所有 ❌ | wheel 安装失败或架构不匹配 | pip3 list 确认已安装;检查架构标签 |
| 仅 Fernet/AES/RSA 失败 | OpenSSL 版本不一致或缺少 legacy provider | 检查系统 OpenSSL 版本;设置 CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1 |
| SSH 散列签名 | import cryptography 时 DLL load failed → .so 签名问题 |
用 NDK 的 binary-sign-tool 重新签名 .so 文件 |
| 仅 X.509 失败 | 时区库或证书解析代码路径问题 | 检查系统 zoneinfo 是否可用 |

参考资源
- OpenHarmony PC 社区:https://harmonypc.csdn.net/
- Python_Package_For_HarmonyOS:https://gitcode.com/OpenHarmonyPCDeveloper/Python_Package_For_HarmonyOS
- gitcode.com/ultrajson
- Python Wheel 规范:https://packaging.python.org/en/latest/specifications/binary-distribution-format/
- musl libc 与 glibc 差异:https://wiki.musl-libc.org/functional-differences-from-glibc.html
更多交流学习,欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
更多推荐




所有评论(0)