使用rust-cpython开发Android库,交叉编译后遇PyModule_Type等符号缺失错误
问题描述
我正在开发一款Android应用,基于Python的Kivy框架开发并使用Buildozer构建,同时开发了基于Rust的扩展库,通过cpython crate实现。从Linux x86_64平台交叉编译到aarch64-linux-android目标时,执行cargo build --target aarch64-linux-android可完成编译,但在手机上运行应用时出现以下错误:
- 未添加
--release标签编译时:
Traceback (most recent call last): File "/home/feluk6174/python/lib_test/.buildozer/android/app/main.py", line 26, in <module> ImportError: dlopen failed: cannot locate symbol "PyModule_Type" referenced by "/data/data/org.test.doxa_test/files/app/doxa_api.so"
- 添加
--release标签编译时:
Traceback (most recent call last): File "/home/feluk6174/python/lib_test/.buildozer/android/app/main.py", line 26, in <module> ImportError: dlopen failed: cannot locate symbol "PyExc_OverflowError" referenced by "/data/data/org.test.doxa_test/files/app/doxa_api.so"
环境说明
参考Mozilla的Rust on Android教程配置了Rust和NDK,之前曾遇到ld: error: unable to find library -lgcc错误,采用GitHub上的修复方案解决后可完成编译,但出现上述符号缺失问题。
相关代码
Rust库代码(lib.rs)
#[macro_use] extern crate cpython; use cpython::{Python, PyResult}; py_module_initializer!(doxa_api, doxa_api, PyInit_doxa_api, |py, m | { m.add(py, "__doc__", "This module is implemented in Rust")?; m.add(py, "test", py_fn!(py, test(a: u8, b:u8) -> PyResult<u8>{ Ok(a as u8+b as u8) } ))?; Ok(()) });
Cargo.toml配置
[package] name = "doxa_api" version = "0.1.0" edition = "2021" [lib] name = "doxa_api" crate-type = ["cdylib"] [dependencies] cpython = {version = "*", features = ["extension-module"]}
.cargo/config.toml配置
[target.aarch64-linux-android] ar = "/home/feluk6174/rust/doxa_api/NDK/arm64/bin/aarch64-linux-android-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/arm64/bin/aarch64-linux-android-clang" [target.armv7-linux-androideabi] ar = "/home/feluk6174/rust/doxa_api/NDK/arm/bin/arm-linux-androideabi-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/arm/bin/arm-linux-androideabi-clang" [target.i686-linux-android] ar = "/home/feluk6174/rust/doxa_api/NDK/x86/bin/i686-linux-android-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/x86/bin/i686-linux-android-clang"
Python应用代码(main.py)
from kivy.app import App from kivy.uix.screenmanager import ScreenManager import kivy.uix.screenmanager from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout import random import doxa_api class TestApp(App): def build(self): sm = ScreenManager() sm.add_widget(Screen(name = "screen")) return sm class Screen(kivy.uix.screenmanager.Screen): def __init__(self, **kwargs): super(Screen, self).__init__(**kwargs) self.main_box = BoxLayout(orientation = "vertical") self.add_widget(self.main_box) self.button = Button(text = "try", on_release = self.test) self.main_box.add_widget(self.button) def test(self, t): print("a") self.button.text = str(doxa_api.test(1, random.randint(0,254))) if __name__ == '__main__': TestApp().run()
版本信息
Python 3.10.9、Kivy 2.1.0、Buildozer 1.5.0、Rust 1.67.0
解决方案
核心原因
Rust扩展库编译时未链接到Buildozer打包的Android Python运行时对应的Python库,导致运行时无法找到Python内部符号。
具体修复步骤
定位Buildozer的Android Python库路径
Buildozer构建时会下载对应架构的Python库,路径通常为:.buildozer/android/platform/python-for-android/build/other_builds/python3/<arch>/android_<arch>/usr/lib/,例如aarch64架构的路径是.buildozer/android/platform/python-for-android/build/other_builds/python3/arm64-v8a/android_arm64_v8a/usr/lib/,找到对应版本的libpython3.10.so文件。修改Rust交叉编译的链接参数
在.cargo/config.toml中为每个Android目标添加rustflags,指定链接到Python库:[target.aarch64-linux-android] ar = "/home/feluk6174/rust/doxa_api/NDK/arm64/bin/aarch64-linux-android-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/arm64/bin/aarch64-linux-android-clang" rustflags = [ "-C", "link-arg=-L/实际路径/buildozer/python/lib", "-C", "link-arg=-lpython3.10", "-C", "link-arg=-Wl,-rpath=$ORIGIN" ] [target.armv7-linux-androideabi] ar = "/home/feluk6174/rust/doxa_api/NDK/arm/bin/arm-linux-androideabi-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/arm/bin/arm-linux-androideabi-clang" rustflags = [ "-C", "link-arg=-L/实际路径/buildozer/python/lib/armeabi-v7a", "-C", "link-arg=-lpython3.10", "-C", "link-arg=-Wl,-rpath=$ORIGIN" ] [target.i686-linux-android] ar = "/home/feluk6174/rust/doxa_api/NDK/x86/bin/i686-linux-android-ar" linker = "/home/feluk6174/rust/doxa_api/NDK/x86/bin/i686-linux-android-clang" rustflags = [ "-C", "link-arg=-L/实际路径/buildozer/python/lib/x86", "-C", "link-arg=-lpython3.10", "-C", "link-arg=-Wl,-rpath=$ORIGIN" ]替换
/实际路径为你本地的Buildozer工作目录路径。对齐cpython crate与Python版本
在Cargo.toml中明确指定cpython版本和对应Python特性,避免版本不兼容:[dependencies] cpython = {version = "0.18", features = ["extension-module", "python310"]}重新编译打包
执行cargo build --target aarch64-linux-android --release重新编译Rust库,将生成的doxa_api.so复制到Kivy应用目录,再用Buildozer重新打包应用。
额外注意事项
- 若链接时提示找不到Python库,检查库文件名是否为
libpython3.so,调整-l参数对应的库名。 - 确保Rust交叉编译使用的NDK版本与Buildozer使用的NDK版本一致,避免链接兼容性问题。
内容的提问来源于stack exchange,提问作者Feluk6174

