鸿蒙PC上装Python C/C三方库移植我踩了一天的坑这篇文章不讲漂亮话只讲我实打实踩过的坑。从bcrypt装到cryptography每一步怎么失败、怎么排查、怎么修全写这里了。新手跟着一步步做不保证一次过但保证出了问题你能自己查。环境说明HarmonyOS NEXTAPI 15aarch64Python 3.12Rust 1.97更多教程示例猫哥的博客:blog.csdn.net/qq8864文章目录0. 先搭好环境0. 先搭好环境0.1 你要装的东西0.2 打开安全开关不然后面各种 Permission denied0.3 配置环境变量0.4 装 pip 的配套工具就两个0.5 记一个后面会反复用到的路径1. 先来个简单的装 bcrypt不走弯路版第一步给 setuptools-rust 打补丁一劳永逸第二步一条命令装 bcrypt第三步验证2. 失败了自己动手排查顺便搞懂原理2.1 报 rustc: No such file or directory → setuptools-rust 补丁没打对2.2 报 undefined symbol: PyBytes_Type → abi3 问题3. 进阶实战装 cryptographymaturin OpenSSL3.1 额外准备先装 OpenSSL3.2 第一个坑maturin 也找不到自己3.3 第二个坑编译找不到 OpenSSL3.4 第三个坑链接找不到 libpython3.12.so3.5 第四个坑装好了但运行时 undefined symbol: OSSL_get_max_threads3.6 cryptography 完整安装命令不分开讲了直接抄4. 总结移植任意 Python C 扩展的通用方法检查清单每次装包前过一遍哪些包容易出问题5. 附完整测试脚本 常见问题5.1 bcrypt 测试脚本5.2 cryptography 测试脚本5.3 常见问题0. 先搭好环境0. 先搭好环境我不会再啰嗦什么是 bcrypt这种废话。直接说你要装什么、为什么。0.1 你要装的东西工具用途怎么装Harmonybrew鸿蒙上的包管理器装 Rust、OpenSSL 等zsh -c $(curl -fsSL https://harmonybrew.atomgit.com/install.sh)ohos-sdk鸿蒙原生编译工具链clang、lldbrew install ohos-sdkllvm-gcc-compat让cc、gcc、ld软链到鸿蒙 LLVMbrew install llvm-gcc-compatdevel-base一键装齐上面两个 make 等brew install devel-baseohos-pip-autosignpip 装完 .so 自动签名不然 import 就报错brew install ohos-pip-autosignopensslcryptography 需要brew install openssl后面才用到一个忠告先把GitNext和DevBox卸载了它们会和 Harmonybrew 打架。0.2 打开安全开关不然后面各种 Permission denied鸿蒙默认不让跑未签名的东西需要手动打开设置 → 系统 → 开发者选项→ 打开开发者选项设置 → 隐私和安全 → 高级→ 打开运行来自非应用市场的扩展程序如果找不到开发者选项设置 → 关于本机 → 连续点 7 次软件版本0.3 配置环境变量装完 Harmonybrew 后终端里先跑这行eval$(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)把它加到 shell 配置文件里免得每次重开终端都要手动跑# 如果你用 zshechoeval $(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)~/.zshrc# 如果你用鸿蒙默认的 mkshechoeval $(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)~/.mkshrc验证brew--version# Homebrew 4.x.xpython3--version# Python 3.12.xrustc--version# rustc 1.x.x0.4 装 pip 的配套工具就两个pip3installsetuptools-rust wheel0.5 记一个后面会反复用到的路径python3-cimport sysconfig; print(sysconfig.get_config_var(LIBDIR))# /storage/Users/currentUser/usr/local/lib ← 记下这个后面 RUSTFLAGS 要用1. 先来个简单的装 bcrypt不走弯路版我踩了一天的坑之后发现正确的方法其实只需要两条命令。先直接给你看正确做法体验一下装成功是什么感觉。第一步给 setuptools-rust 打补丁一劳永逸找到_utils.py的位置python3-cimport setuptools_rust._utils; print(setuptools_rust._utils.__file__)# /storage/.../setuptools_rust/_utils.py用编辑器打开这个文件**在开头import之后、run_subprocess函数之前**加上一个辅助函数然后改两个地方加在文件里复制粘贴即可importshutil# 确保文件头部已经有这个 import没有就加上def_resolve_executable(cmd,env):把命令名转成绝对路径鸿蒙上 subprocess 不搜 PATHifisinstance(cmd,(list,tuple))andcmd:execmd[0]ifisinstance(exe,str)andos.sepnotinexe:resolvedshutil.which(exe,pathenv.get(PATH)ifenvelseNone)ifresolved:cmdlist(cmd)cmd[0]resolvedreturncmd然后改两个函数的调用搜索run_subprocess和check_subprocess_output在return前面加一行defrun_subprocess(*args,envNone,**kwargs):ifisinstance(env,Env):envenv.env kwargs[env]env argstuple(_resolve_executable(a,env)forainargs)# ← 加这行returnsubprocess.run(*args,**kwargs)defcheck_subprocess_output(*args,envNone,**kwargs):ifisinstance(env,Env):envenv.env kwargs[env]env argstuple(_resolve_executable(a,env)forainargs)# ← 加这行returncast(str,subprocess.check_output(*args,**kwargs))为什么是这个文件因为 pip 装 Rust 扩展时setuptools-rust 会调subprocess.run([rustc, ...])。正常 Linux 上会搜 PATH 找到rustc但鸿蒙的posix_spawn不走 PATH所以报错。这里的修改等价于每次调子进程之前先把命令名转成绝对路径。第二步一条命令装 bcryptPYO3_PYTHON/storage/Users/currentUser/usr/local/bin/python3.12\RUSTFLAGS-L /storage/Users/currentUser/usr/local/lib -l python3.12\pipinstall--no-binary bcrypt bcrypt --no-build-isolation-v这条命令做完的事情从 PyPI 拉 bcrypt 源码不用手动下载用你修改过的 setuptools-rust 调用rustc补丁生效 ✓通过RUSTFLAGS把libpython3.12.so链接进去abi3 问题解决 ✓签名ohos-pip-autosign 自动处理 ✓装好第三步验证python3-cimport bcrypt; print(版本:, bcrypt.__version__); print(OK)如果看到版本: 5.0.0恭喜你装好了。2. 失败了自己动手排查顺便搞懂原理如果上一条命令跑失败了别慌。我当初每一步都失败了每个错长什么样写下面。2.1 报rustc: No such file or directory→ setuptools-rust 补丁没打对错误长这样Caused by: No such file or directory (os error 2)或者Error: Failed to find rustc. Check that the Rust toolchain is installed.但你已经rustc --version确认装好了对吧问题不是 rustc 没装是subprocess 在鸿蒙上找不到 rustc。快速验证是不是这个问题python3-c import subprocess, os env os.environ.copy() # 用绝对路径调 —— 这应该成功 r subprocess.run([/storage/Users/currentUser/.harmonybrew/bin/rustc, --version], capture_outputTrue, textTrue, envenv) print(绝对路径:, r.stdout.strip()) # 只用名字调 —— 鸿蒙上会失败 try: r subprocess.run([rustc, --version], capture_outputTrue, textTrue, envenv) print(只用名字:, r.stdout.strip()) except FileNotFoundError as e: print(只用名字失败了:, e) # 这就是鸿蒙的 bug 修复回到 第一步 检查补丁是否打对。重点是_resolve_executable函数的缩进和位置。2.2 报undefined symbol: PyBytes_Type→ abi3 问题错误长这样ImportError: .../bcrypt/_bcrypt.cpython-312-aarch64-linux-ohos.so: undefined symbol: PyBytes_Type一句话解释bcrypt 的 Cargo.toml 里写了features [abi3]意思是编译出的 .so 不直接依赖 libpython期望运行时从主程序里借符号。但鸿蒙的dlopen不像标准 Linux 那样会让主程序符号对动态库可见所以 .so 加载时找不到 Py* 符号。怎么验证是这个原因# 用 LD_PRELOAD 强制加载 libpython如果这样能成功就是 abi3 问题LD_PRELOAD/storage/Users/currentUser/usr/local/lib/libpython3.12.so\python3-cimport bcrypt; print(OK)如果这时候成功确认了。修复RUSTFLAGS-L /storage/Users/currentUser/usr/local/lib -l python3.12—— 告诉 linker 把 libpython3.12.so 链接进去。生成的 .so 里会有NEEDED libpython3.12.so运行时自动加载。补充为什么不直接写死build.rs两个方案都可以但我推荐 RUSTFLAGS因为不用手动下载源码对所有用 abi3 的包都有效一行命令搞定不留下修改痕迹只有一种情况需要 build.rs你要把这个包做成二进制分发每次安装时不想带环境变量。3. 进阶实战装 cryptographymaturin OpenSSLbcrypt 还算简单的——没有 C 依赖只有 Rust。cryptography就不一样了它用maturin做构建工具还要OpenSSL。多出了好几个坑。3.1 额外准备先装 OpenSSLbrewinstallopenssl pkg-config验证 OpenSSL 能找到exportPKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATHpkg-config--libs--cflagsopenssl# 应该输出类似-I/storage/.../include -L/storage/.../lib -lssl -lcrypto3.2 第一个坑maturin 也找不到自己错误长这样Preparing metadata (pyproject.toml) ... error error: maturin failed Caused by: Failed to find maturin. Is maturin installed?和 setuptools-rust 的问题一模一样——maturin 的 Python 代码也是用subprocess.run([maturin, ...])调命令行工具。鸿蒙上同样找不到。修复修改maturin/__init__.py# 找到文件位置python3-cimport maturin; print(maturin.__file__)# /storage/.../maturin/__init__.py# 找到 maturin 命令的绝对路径whichmaturin# /storage/Users/currentUser/usr/local/bin/maturin打开 maturin 的__init__.py找到调子进程的地方大概在第 70-90 行的_get_env函数里把command[maturin,pep517,write-metadata,...]改成command[/storage/Users/currentUser/usr/local/bin/maturin,pep517,write-metadata,...]如果有多处全改。注意安装时必须加--no-build-isolation否则 pip 会在隔离环境里重新下载一份没改过的 maturin。3.3 第二个坑编译找不到 OpenSSL错误长这样error: failed to run custom build command for openssl-sys v0.9.xx Caused by: Could not find directory of OpenSSL installation原因openssl-sys这个 Rust crate 需要找到 OpenSSL 的头文件和库来编译。修复安装时传OPENSSL_DIR和PKG_CONFIG_PATHPKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH\OPENSSL_DIR/storage/Users/currentUser/.harmonybrew/opt/openssl\pip3installcryptography --no-build-isolation3.4 第三个坑链接找不到 libpython3.12.so错误长这样rust-lld: error: unable to find library -lpython3.12原因cryptography 也用了 abi3和 bcrypt 一样需要链接 libpython。修复加LIBRARY_PATHLIBRARY_PATH/storage/Users/currentUser/usr/local/lib:$LIBRARY_PATH\PKG_CONFIG_PATH...\OPENSSL_DIR...\pip3installcryptography --no-build-isolation3.5 第四个坑装好了但运行时undefined symbol: OSSL_get_max_threads错误长这样ImportError: .../cryptography/hazmat/bindings/_openssl.abi3.so: undefined symbol: OSSL_get_max_threads原因编译时用的是 Harmonybrew 的 OpenSSL 3.6.3有OSSL_get_max_threads。运行时动态链接器找的是系统自带的旧版 OpenSSL没有这个符号。编译时 OK: /harmonybrew/opt/openssl/lib/libcrypto.so (3.6.3) 运行时 ❌: /usr/lib/libcrypto.so (旧版没 OSSL_get_max_threads)修复运行前设LD_LIBRARY_PATH让动态链接器先找 Harmonybrew 的 OpenSSLLD_LIBRARY_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib:$LD_LIBRARY_PATH\python3-cfrom cryptography.fernet import Fernet; print(OK)建议加到 shell 配置里exportLD_LIBRARY_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib:$LD_LIBRARY_PATH更好的方案编译时嵌入 RPATH推荐一劳永逸设LD_LIBRARY_PATH有两个麻烦每次开新终端都得记得设如果多个 Python C 扩展依赖不同版本的 OpenSSL全局LD_LIBRARY_PATH可能冲突更好的做法是编译时直接把 OpenSSL 库路径嵌进.so文件里通过 RUMPATH 或 RPATH。这样编译好的 wheel 自带查找路径运行时不用任何环境变量RUSTFLAGS-C link-args-Wl,-rpath,/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\PKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig\LIBRARY_PATH/storage/Users/currentUser/usr/local/lib:/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\pipinstallcryptography --no-binary cryptography --no-build-isolation --force-reinstallRUSTFLAGS中的-Wl,-rpath,...告诉链接器把该路径写入生成的.so的RUNPATH段。可以用readelf验证readelf-d_rust.abi3.so|grepRUNPATH# 输出: 0x000000000000001d (RUNPATH) Library runpath: [.../openssl3/3.6.3/lib]验证通过后直接运行无需LD_LIBRARY_PATHpython3-cfrom cryptography.fernet import Fernet; print(cryptography 导入成功)# ✅ 直接成功不需要 LD_LIBRARY_PATH原理动态链接器加载共享库时先搜 RPATH/RUNPATH如果设了再搜LD_LIBRARY_PATH最后搜系统默认路径。编译时-Wl,-rpath写入的是 RUNPATH优先级比LD_LIBRARY_PATH低但足够覆盖系统路径差异。3.6 cryptography 完整安装命令不分开讲了直接抄先搞定 setuptools-rust 补丁和 maturin 补丁见 3.2 节然后一条命令推荐 RPATH 方案一劳永逸RUSTFLAGS-C link-args-Wl,-rpath,/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\LIBRARY_PATH/storage/Users/currentUser/usr/local/lib:/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib:$LIBRARY_PATH\PKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH\OPENSSL_DIR/storage/Users/currentUser/.harmonybrew/opt/openssl\pip3installcryptography --no-binary cryptography --no-build-isolation成功输出大概长这样$RUSTFLAGS...LIBRARY_PATH...PKG_CONFIG_PATH...OPENSSL_DIR...pip3installcryptography --no-binary cryptography --no-build-isolation Processing ./cryptography Preparing metadata(pyproject.toml)...doneBuilding wheelforcryptography(pyproject.toml)...doneCreated wheel: cryptography-49.0.0-cp312-abi3-linux_aarch64.whl Installing...doneSuccessfully installed cryptography-49.0.0安装后一步验证不需要设LD_LIBRARY_PATHpython3-cfrom cryptography.fernet import Fernet; print(cryptography 导入成功)# ✅ 直接成功不需要 LD_LIBRARY_PATH核心思路RUSTFLAGS中的-C link-args-Wl,-rpath,...让链接器把 OpenSSL 库路径写入.so文件的 RUNPATH 段运行时动态链接器自动去那里找无需任何环境变量。4. 总结移植任意 Python C 扩展的通用方法通篇下来你会发现问题其实可以归成三个独立阶段每个阶段有自己独特的错误特征阶段出问题时长什么样怎么修① 构建阶段子进程No such file or directory、Failed to find xxx打补丁setuptools-rust 改_utils.pymaturin 改__init__.py② 编译/链接阶段abi3undefined symbol: Py*、unable to find library设RUSTFLAGS、LIBRARY_PATH③ 运行时本地库undefined symbol: OSSL_*编译时RUSTFLAGS嵌入 RPATH一劳永逸或设LD_LIBRARY_PATH临时检查清单每次装包前过一遍setuptools-rust/_utils.py打补丁了没一劳永逸打一次就行maturin/__init__.py打补丁了没如果你要装 maturin 构建的包这个包用 abi3 了吗→ 查Cargo.toml或.whl文件名是否含abi3用了就设RUSTFLAGS-L LIBDIR -l python3.12找 libpython-C link-args-Wl,-rpath,LIBS_DIR嵌入运行时库路径有没有本地 C 依赖如 OpenSSL→brew install并设PKG_CONFIG_PATH装的时候加--no-build-isolation了吗补丁在隔离环境不生效运行时有问题→ 先试readelf -d _rust.abi3.so | grep RUNPATH检查 RPATH 是否已嵌入没嵌入就设LD_LIBRARY_PATH哪些包容易出问题特征Rust C 扩展 可能用了 abi3 可能需要子进程编译cryptography— maturin OpenSSLtokenizers— Hugging Face 分词器pydantic-core— Pydantic v2ruff— Python linterorjson— 快速 JSONuv— 包管理器5. 附完整测试脚本 常见问题5.1 bcrypt 测试脚本保存为test_bcrypt.py#!/usr/bin/env python3 bcrypt 5.0.0 功能测试脚本 importbcrypt passwordbmysecretpasswordsaltbcrypt.gensalt(rounds10)hashedbcrypt.hashpw(password,salt)assertbcrypt.checkpw(password,hashed),正确密码验证失败assertnotbcrypt.checkpw(bwrong,hashed),错误密码应该被拒绝print(f版本:{bcrypt.__version__})print(f哈希值:{hashed})print(所有测试通过)5.2 cryptography 测试脚本保存为test_cryptography.py#!/usr/bin/env python3cryptography 功能测试fromcryptography.fernetimportFernetfromcryptography.hazmat.primitivesimporthashes# 测试对称加密keyFernet.generate_key()fFernet(key)tokenf.encrypt(bHello HarmonyOS!)assertf.decrypt(token)bHello HarmonyOS!print(✅ Fernet 对称加密/解密通过)# 测试哈希digesthashes.Hash(hashes.SHA256())digest.update(btest)digest.finalize()print(✅ SHA256 哈希通过)print(所有 cryptography 测试通过)5.3 常见问题Q: 怎么看一个包用了 setuptools-rust 还是 maturin看它的pyproject.toml# setuptools-rust [build-system] requires [setuptools, setuptools-rust] build-backend setuptools.build_meta # maturin [build-system] requires [maturin1.5,2.0] build-backend maturinQ: 怎么看一个包用了 abi3# 下载源码包后grep-rabi3\|py-limited-api.或者看 PyPI 上的.whl文件名是否包含abi3。Q: 升级 setuptools-rust 后补丁会丢吗会。升级后重新打一次补丁。建议备份cp/path/to/setuptools_rust/_utils.py ~/patches/_utils_patched.py# 升级后cp~/patches/_utils_patched.py /path/to/setuptools_rust/_utils.pyQ: 为什么有的教程让pip wheel . --no-build-isolation -w dist/pip wheel 只编译不安装产物是.whl文件。如果你只是pip install一步到位不需要管这个。Q: 怎么检查 .so 文件的链接状态# 鸿蒙上没有 readelf用 Python 检查python3-c import sys with open(/path/to/xxx.so, rb) as f: data f.read() if bpython in data.lower(): print(可能需要 libpython) else: print(可能是 abi3 模式) 最后说两句鸿蒙虽然对标 Linux但底层实现posix_spawn、dlopen有不少细节差异。遇到问题别上来就怀疑是不是我没装对先想想这个行为在 Linux 上是不是也一样 ——很多时候不是你配错了是系统行为本来就不同。更多教程示例猫哥的博客:blog.csdn.net/qq8864
鸿蒙 PC(HarmonyOS)编译安装 Python C 扩展库完全指南(踩坑笔记)
鸿蒙PC上装Python C/C三方库移植我踩了一天的坑这篇文章不讲漂亮话只讲我实打实踩过的坑。从bcrypt装到cryptography每一步怎么失败、怎么排查、怎么修全写这里了。新手跟着一步步做不保证一次过但保证出了问题你能自己查。环境说明HarmonyOS NEXTAPI 15aarch64Python 3.12Rust 1.97更多教程示例猫哥的博客:blog.csdn.net/qq8864文章目录0. 先搭好环境0. 先搭好环境0.1 你要装的东西0.2 打开安全开关不然后面各种 Permission denied0.3 配置环境变量0.4 装 pip 的配套工具就两个0.5 记一个后面会反复用到的路径1. 先来个简单的装 bcrypt不走弯路版第一步给 setuptools-rust 打补丁一劳永逸第二步一条命令装 bcrypt第三步验证2. 失败了自己动手排查顺便搞懂原理2.1 报 rustc: No such file or directory → setuptools-rust 补丁没打对2.2 报 undefined symbol: PyBytes_Type → abi3 问题3. 进阶实战装 cryptographymaturin OpenSSL3.1 额外准备先装 OpenSSL3.2 第一个坑maturin 也找不到自己3.3 第二个坑编译找不到 OpenSSL3.4 第三个坑链接找不到 libpython3.12.so3.5 第四个坑装好了但运行时 undefined symbol: OSSL_get_max_threads3.6 cryptography 完整安装命令不分开讲了直接抄4. 总结移植任意 Python C 扩展的通用方法检查清单每次装包前过一遍哪些包容易出问题5. 附完整测试脚本 常见问题5.1 bcrypt 测试脚本5.2 cryptography 测试脚本5.3 常见问题0. 先搭好环境0. 先搭好环境我不会再啰嗦什么是 bcrypt这种废话。直接说你要装什么、为什么。0.1 你要装的东西工具用途怎么装Harmonybrew鸿蒙上的包管理器装 Rust、OpenSSL 等zsh -c $(curl -fsSL https://harmonybrew.atomgit.com/install.sh)ohos-sdk鸿蒙原生编译工具链clang、lldbrew install ohos-sdkllvm-gcc-compat让cc、gcc、ld软链到鸿蒙 LLVMbrew install llvm-gcc-compatdevel-base一键装齐上面两个 make 等brew install devel-baseohos-pip-autosignpip 装完 .so 自动签名不然 import 就报错brew install ohos-pip-autosignopensslcryptography 需要brew install openssl后面才用到一个忠告先把GitNext和DevBox卸载了它们会和 Harmonybrew 打架。0.2 打开安全开关不然后面各种 Permission denied鸿蒙默认不让跑未签名的东西需要手动打开设置 → 系统 → 开发者选项→ 打开开发者选项设置 → 隐私和安全 → 高级→ 打开运行来自非应用市场的扩展程序如果找不到开发者选项设置 → 关于本机 → 连续点 7 次软件版本0.3 配置环境变量装完 Harmonybrew 后终端里先跑这行eval$(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)把它加到 shell 配置文件里免得每次重开终端都要手动跑# 如果你用 zshechoeval $(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)~/.zshrc# 如果你用鸿蒙默认的 mkshechoeval $(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)~/.mkshrc验证brew--version# Homebrew 4.x.xpython3--version# Python 3.12.xrustc--version# rustc 1.x.x0.4 装 pip 的配套工具就两个pip3installsetuptools-rust wheel0.5 记一个后面会反复用到的路径python3-cimport sysconfig; print(sysconfig.get_config_var(LIBDIR))# /storage/Users/currentUser/usr/local/lib ← 记下这个后面 RUSTFLAGS 要用1. 先来个简单的装 bcrypt不走弯路版我踩了一天的坑之后发现正确的方法其实只需要两条命令。先直接给你看正确做法体验一下装成功是什么感觉。第一步给 setuptools-rust 打补丁一劳永逸找到_utils.py的位置python3-cimport setuptools_rust._utils; print(setuptools_rust._utils.__file__)# /storage/.../setuptools_rust/_utils.py用编辑器打开这个文件**在开头import之后、run_subprocess函数之前**加上一个辅助函数然后改两个地方加在文件里复制粘贴即可importshutil# 确保文件头部已经有这个 import没有就加上def_resolve_executable(cmd,env):把命令名转成绝对路径鸿蒙上 subprocess 不搜 PATHifisinstance(cmd,(list,tuple))andcmd:execmd[0]ifisinstance(exe,str)andos.sepnotinexe:resolvedshutil.which(exe,pathenv.get(PATH)ifenvelseNone)ifresolved:cmdlist(cmd)cmd[0]resolvedreturncmd然后改两个函数的调用搜索run_subprocess和check_subprocess_output在return前面加一行defrun_subprocess(*args,envNone,**kwargs):ifisinstance(env,Env):envenv.env kwargs[env]env argstuple(_resolve_executable(a,env)forainargs)# ← 加这行returnsubprocess.run(*args,**kwargs)defcheck_subprocess_output(*args,envNone,**kwargs):ifisinstance(env,Env):envenv.env kwargs[env]env argstuple(_resolve_executable(a,env)forainargs)# ← 加这行returncast(str,subprocess.check_output(*args,**kwargs))为什么是这个文件因为 pip 装 Rust 扩展时setuptools-rust 会调subprocess.run([rustc, ...])。正常 Linux 上会搜 PATH 找到rustc但鸿蒙的posix_spawn不走 PATH所以报错。这里的修改等价于每次调子进程之前先把命令名转成绝对路径。第二步一条命令装 bcryptPYO3_PYTHON/storage/Users/currentUser/usr/local/bin/python3.12\RUSTFLAGS-L /storage/Users/currentUser/usr/local/lib -l python3.12\pipinstall--no-binary bcrypt bcrypt --no-build-isolation-v这条命令做完的事情从 PyPI 拉 bcrypt 源码不用手动下载用你修改过的 setuptools-rust 调用rustc补丁生效 ✓通过RUSTFLAGS把libpython3.12.so链接进去abi3 问题解决 ✓签名ohos-pip-autosign 自动处理 ✓装好第三步验证python3-cimport bcrypt; print(版本:, bcrypt.__version__); print(OK)如果看到版本: 5.0.0恭喜你装好了。2. 失败了自己动手排查顺便搞懂原理如果上一条命令跑失败了别慌。我当初每一步都失败了每个错长什么样写下面。2.1 报rustc: No such file or directory→ setuptools-rust 补丁没打对错误长这样Caused by: No such file or directory (os error 2)或者Error: Failed to find rustc. Check that the Rust toolchain is installed.但你已经rustc --version确认装好了对吧问题不是 rustc 没装是subprocess 在鸿蒙上找不到 rustc。快速验证是不是这个问题python3-c import subprocess, os env os.environ.copy() # 用绝对路径调 —— 这应该成功 r subprocess.run([/storage/Users/currentUser/.harmonybrew/bin/rustc, --version], capture_outputTrue, textTrue, envenv) print(绝对路径:, r.stdout.strip()) # 只用名字调 —— 鸿蒙上会失败 try: r subprocess.run([rustc, --version], capture_outputTrue, textTrue, envenv) print(只用名字:, r.stdout.strip()) except FileNotFoundError as e: print(只用名字失败了:, e) # 这就是鸿蒙的 bug 修复回到 第一步 检查补丁是否打对。重点是_resolve_executable函数的缩进和位置。2.2 报undefined symbol: PyBytes_Type→ abi3 问题错误长这样ImportError: .../bcrypt/_bcrypt.cpython-312-aarch64-linux-ohos.so: undefined symbol: PyBytes_Type一句话解释bcrypt 的 Cargo.toml 里写了features [abi3]意思是编译出的 .so 不直接依赖 libpython期望运行时从主程序里借符号。但鸿蒙的dlopen不像标准 Linux 那样会让主程序符号对动态库可见所以 .so 加载时找不到 Py* 符号。怎么验证是这个原因# 用 LD_PRELOAD 强制加载 libpython如果这样能成功就是 abi3 问题LD_PRELOAD/storage/Users/currentUser/usr/local/lib/libpython3.12.so\python3-cimport bcrypt; print(OK)如果这时候成功确认了。修复RUSTFLAGS-L /storage/Users/currentUser/usr/local/lib -l python3.12—— 告诉 linker 把 libpython3.12.so 链接进去。生成的 .so 里会有NEEDED libpython3.12.so运行时自动加载。补充为什么不直接写死build.rs两个方案都可以但我推荐 RUSTFLAGS因为不用手动下载源码对所有用 abi3 的包都有效一行命令搞定不留下修改痕迹只有一种情况需要 build.rs你要把这个包做成二进制分发每次安装时不想带环境变量。3. 进阶实战装 cryptographymaturin OpenSSLbcrypt 还算简单的——没有 C 依赖只有 Rust。cryptography就不一样了它用maturin做构建工具还要OpenSSL。多出了好几个坑。3.1 额外准备先装 OpenSSLbrewinstallopenssl pkg-config验证 OpenSSL 能找到exportPKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATHpkg-config--libs--cflagsopenssl# 应该输出类似-I/storage/.../include -L/storage/.../lib -lssl -lcrypto3.2 第一个坑maturin 也找不到自己错误长这样Preparing metadata (pyproject.toml) ... error error: maturin failed Caused by: Failed to find maturin. Is maturin installed?和 setuptools-rust 的问题一模一样——maturin 的 Python 代码也是用subprocess.run([maturin, ...])调命令行工具。鸿蒙上同样找不到。修复修改maturin/__init__.py# 找到文件位置python3-cimport maturin; print(maturin.__file__)# /storage/.../maturin/__init__.py# 找到 maturin 命令的绝对路径whichmaturin# /storage/Users/currentUser/usr/local/bin/maturin打开 maturin 的__init__.py找到调子进程的地方大概在第 70-90 行的_get_env函数里把command[maturin,pep517,write-metadata,...]改成command[/storage/Users/currentUser/usr/local/bin/maturin,pep517,write-metadata,...]如果有多处全改。注意安装时必须加--no-build-isolation否则 pip 会在隔离环境里重新下载一份没改过的 maturin。3.3 第二个坑编译找不到 OpenSSL错误长这样error: failed to run custom build command for openssl-sys v0.9.xx Caused by: Could not find directory of OpenSSL installation原因openssl-sys这个 Rust crate 需要找到 OpenSSL 的头文件和库来编译。修复安装时传OPENSSL_DIR和PKG_CONFIG_PATHPKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH\OPENSSL_DIR/storage/Users/currentUser/.harmonybrew/opt/openssl\pip3installcryptography --no-build-isolation3.4 第三个坑链接找不到 libpython3.12.so错误长这样rust-lld: error: unable to find library -lpython3.12原因cryptography 也用了 abi3和 bcrypt 一样需要链接 libpython。修复加LIBRARY_PATHLIBRARY_PATH/storage/Users/currentUser/usr/local/lib:$LIBRARY_PATH\PKG_CONFIG_PATH...\OPENSSL_DIR...\pip3installcryptography --no-build-isolation3.5 第四个坑装好了但运行时undefined symbol: OSSL_get_max_threads错误长这样ImportError: .../cryptography/hazmat/bindings/_openssl.abi3.so: undefined symbol: OSSL_get_max_threads原因编译时用的是 Harmonybrew 的 OpenSSL 3.6.3有OSSL_get_max_threads。运行时动态链接器找的是系统自带的旧版 OpenSSL没有这个符号。编译时 OK: /harmonybrew/opt/openssl/lib/libcrypto.so (3.6.3) 运行时 ❌: /usr/lib/libcrypto.so (旧版没 OSSL_get_max_threads)修复运行前设LD_LIBRARY_PATH让动态链接器先找 Harmonybrew 的 OpenSSLLD_LIBRARY_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib:$LD_LIBRARY_PATH\python3-cfrom cryptography.fernet import Fernet; print(OK)建议加到 shell 配置里exportLD_LIBRARY_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib:$LD_LIBRARY_PATH更好的方案编译时嵌入 RPATH推荐一劳永逸设LD_LIBRARY_PATH有两个麻烦每次开新终端都得记得设如果多个 Python C 扩展依赖不同版本的 OpenSSL全局LD_LIBRARY_PATH可能冲突更好的做法是编译时直接把 OpenSSL 库路径嵌进.so文件里通过 RUMPATH 或 RPATH。这样编译好的 wheel 自带查找路径运行时不用任何环境变量RUSTFLAGS-C link-args-Wl,-rpath,/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\PKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig\LIBRARY_PATH/storage/Users/currentUser/usr/local/lib:/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\pipinstallcryptography --no-binary cryptography --no-build-isolation --force-reinstallRUSTFLAGS中的-Wl,-rpath,...告诉链接器把该路径写入生成的.so的RUNPATH段。可以用readelf验证readelf-d_rust.abi3.so|grepRUNPATH# 输出: 0x000000000000001d (RUNPATH) Library runpath: [.../openssl3/3.6.3/lib]验证通过后直接运行无需LD_LIBRARY_PATHpython3-cfrom cryptography.fernet import Fernet; print(cryptography 导入成功)# ✅ 直接成功不需要 LD_LIBRARY_PATH原理动态链接器加载共享库时先搜 RPATH/RUNPATH如果设了再搜LD_LIBRARY_PATH最后搜系统默认路径。编译时-Wl,-rpath写入的是 RUNPATH优先级比LD_LIBRARY_PATH低但足够覆盖系统路径差异。3.6 cryptography 完整安装命令不分开讲了直接抄先搞定 setuptools-rust 补丁和 maturin 补丁见 3.2 节然后一条命令推荐 RPATH 方案一劳永逸RUSTFLAGS-C link-args-Wl,-rpath,/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib\LIBRARY_PATH/storage/Users/currentUser/usr/local/lib:/storage/Users/currentUser/.harmonybrew/Cellar/openssl3/3.6.3/lib:$LIBRARY_PATH\PKG_CONFIG_PATH/storage/Users/currentUser/.harmonybrew/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH\OPENSSL_DIR/storage/Users/currentUser/.harmonybrew/opt/openssl\pip3installcryptography --no-binary cryptography --no-build-isolation成功输出大概长这样$RUSTFLAGS...LIBRARY_PATH...PKG_CONFIG_PATH...OPENSSL_DIR...pip3installcryptography --no-binary cryptography --no-build-isolation Processing ./cryptography Preparing metadata(pyproject.toml)...doneBuilding wheelforcryptography(pyproject.toml)...doneCreated wheel: cryptography-49.0.0-cp312-abi3-linux_aarch64.whl Installing...doneSuccessfully installed cryptography-49.0.0安装后一步验证不需要设LD_LIBRARY_PATHpython3-cfrom cryptography.fernet import Fernet; print(cryptography 导入成功)# ✅ 直接成功不需要 LD_LIBRARY_PATH核心思路RUSTFLAGS中的-C link-args-Wl,-rpath,...让链接器把 OpenSSL 库路径写入.so文件的 RUNPATH 段运行时动态链接器自动去那里找无需任何环境变量。4. 总结移植任意 Python C 扩展的通用方法通篇下来你会发现问题其实可以归成三个独立阶段每个阶段有自己独特的错误特征阶段出问题时长什么样怎么修① 构建阶段子进程No such file or directory、Failed to find xxx打补丁setuptools-rust 改_utils.pymaturin 改__init__.py② 编译/链接阶段abi3undefined symbol: Py*、unable to find library设RUSTFLAGS、LIBRARY_PATH③ 运行时本地库undefined symbol: OSSL_*编译时RUSTFLAGS嵌入 RPATH一劳永逸或设LD_LIBRARY_PATH临时检查清单每次装包前过一遍setuptools-rust/_utils.py打补丁了没一劳永逸打一次就行maturin/__init__.py打补丁了没如果你要装 maturin 构建的包这个包用 abi3 了吗→ 查Cargo.toml或.whl文件名是否含abi3用了就设RUSTFLAGS-L LIBDIR -l python3.12找 libpython-C link-args-Wl,-rpath,LIBS_DIR嵌入运行时库路径有没有本地 C 依赖如 OpenSSL→brew install并设PKG_CONFIG_PATH装的时候加--no-build-isolation了吗补丁在隔离环境不生效运行时有问题→ 先试readelf -d _rust.abi3.so | grep RUNPATH检查 RPATH 是否已嵌入没嵌入就设LD_LIBRARY_PATH哪些包容易出问题特征Rust C 扩展 可能用了 abi3 可能需要子进程编译cryptography— maturin OpenSSLtokenizers— Hugging Face 分词器pydantic-core— Pydantic v2ruff— Python linterorjson— 快速 JSONuv— 包管理器5. 附完整测试脚本 常见问题5.1 bcrypt 测试脚本保存为test_bcrypt.py#!/usr/bin/env python3 bcrypt 5.0.0 功能测试脚本 importbcrypt passwordbmysecretpasswordsaltbcrypt.gensalt(rounds10)hashedbcrypt.hashpw(password,salt)assertbcrypt.checkpw(password,hashed),正确密码验证失败assertnotbcrypt.checkpw(bwrong,hashed),错误密码应该被拒绝print(f版本:{bcrypt.__version__})print(f哈希值:{hashed})print(所有测试通过)5.2 cryptography 测试脚本保存为test_cryptography.py#!/usr/bin/env python3cryptography 功能测试fromcryptography.fernetimportFernetfromcryptography.hazmat.primitivesimporthashes# 测试对称加密keyFernet.generate_key()fFernet(key)tokenf.encrypt(bHello HarmonyOS!)assertf.decrypt(token)bHello HarmonyOS!print(✅ Fernet 对称加密/解密通过)# 测试哈希digesthashes.Hash(hashes.SHA256())digest.update(btest)digest.finalize()print(✅ SHA256 哈希通过)print(所有 cryptography 测试通过)5.3 常见问题Q: 怎么看一个包用了 setuptools-rust 还是 maturin看它的pyproject.toml# setuptools-rust [build-system] requires [setuptools, setuptools-rust] build-backend setuptools.build_meta # maturin [build-system] requires [maturin1.5,2.0] build-backend maturinQ: 怎么看一个包用了 abi3# 下载源码包后grep-rabi3\|py-limited-api.或者看 PyPI 上的.whl文件名是否包含abi3。Q: 升级 setuptools-rust 后补丁会丢吗会。升级后重新打一次补丁。建议备份cp/path/to/setuptools_rust/_utils.py ~/patches/_utils_patched.py# 升级后cp~/patches/_utils_patched.py /path/to/setuptools_rust/_utils.pyQ: 为什么有的教程让pip wheel . --no-build-isolation -w dist/pip wheel 只编译不安装产物是.whl文件。如果你只是pip install一步到位不需要管这个。Q: 怎么检查 .so 文件的链接状态# 鸿蒙上没有 readelf用 Python 检查python3-c import sys with open(/path/to/xxx.so, rb) as f: data f.read() if bpython in data.lower(): print(可能需要 libpython) else: print(可能是 abi3 模式) 最后说两句鸿蒙虽然对标 Linux但底层实现posix_spawn、dlopen有不少细节差异。遇到问题别上来就怀疑是不是我没装对先想想这个行为在 Linux 上是不是也一样 ——很多时候不是你配错了是系统行为本来就不同。更多教程示例猫哥的博客:blog.csdn.net/qq8864