linux下将Python3解释器交叉编译移植到android平台

02/07 19:00
阅读数 1.2K

已知资料:

  • http://www.srplab.com/cn/files/others/compile/cross_compiling_python_for_android.html Cross Compiling Python for Android
  • https://m.2cto.com/kf/201511/448789.html 在arm上使用python-2.7.10
上述资料主要是在移植python2.7版本,具体移植python3的版本资料并不多。
 
起初打算移植python3.5.6版本,但是发现python3.5.6的移植性似乎不好,在解决完python主程序和libpython3.5.so的编译后,所有扩展均因为各种找不到c函数或者找不到python的对象或函数名之类的问题无法编译。后决定编译python3.7.1版本。记录如下。

编译记录:

  • 源码从python.org下载。
  • 首先编译Linux版本的Python3.7.1并安装。我选择安装到~/opt目录下,以加入环境变量的形式替代系统使用的python3.5。命令如下:
  ~/projects tar xvf ~/Download/Python-3.7.1.tar.xz -C .
  ~/projects cd Python-3.7.1
  ~/projects/Python-3.7.1 mkdir build.pc
  ~/projects/Python-3.7.1 cd build.pc
  ~/projects/Python-3.7.1/build.pc ../configure --prefix=/home/xys/opt/Python-3.7.1
  ~/projects/Python-3.7.1/build.pc make
  ~/projects/Python-3.7.1/build.pc make install
  • 将python加入环境变量。由于我同时使用bash和zsh,于是做了两个动作:
首先建立公共的rc脚本~/rc.public,加入
#!/bin/sh                                                                                                                                      
# python
export PATH=/home/xys/opt/Python-3.7.1/bin:$PATH
 
然后在~/.zshrc中加入
 
# include ~/rc.public
if [ -f ~/rc.public ]; then
    . ~/rc.public
fi 
 
bashrc类似,我还没加。
之后可以在终端输入python自动补全中发现python3.7程序。
  • 编译安卓版本。以32位为例,
#!/bin/bash
# 使用android-ndk-r10e,其目录设置环境变量ANDROID_NDK。
# 我将当前目录定为环境变量
 
WORKSPACE=`pwd`
if [[ -z "$ANDROID_NDK" ]]; then
   ANDROID_NDK=/opt/local/android-ndk-r10e
fi
 
ANDROID_SYSROOT=$ANDROID_NDK/platforms/android-21/arch-arm
ANDROID64_SYSROOT=$ANDROID_NDK/platforms/android-21/arch-arm64

cd $WORKSPACE
echo "building with $CMAKE_SYSTEM, processor=$CMAKE_SYSTEM_PROCESSOR, pwd=$WORKSPACE"

# generate Makefile
if [[ "$CMAKE_SYSTEM_PROCESSOR" =~ "x86" ]]; then
    # x86 or x86_64

    mkdir -p build.pc
    cd build.pc
    ../configure --enable-shared --enable-ipv6 --prefix=$WORKSPACE/out/x86
elif [[ "$CMAKE_SYSTEM_PROCESSOR" =~ "armv7" ]]; then
   # 64位时这里要修改为$ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin
   export PATH=$ANDROID_NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK:$ANDROID_NDK/tools:/usr/local/bin:/usr/bin:/bin:$PATH
   # 64位时这里要修改为"aarch64-linux-android-"
   CROSS_COMPILER_PREFIX="arm-linux-androideabi-"
   # 64位时要改成aarch64
   export ARCH="armeabi"
   # android 4.4及之后版本有要求可执行程序为PIE程序
   export CC="${CROSS_COMPILER_PREFIX}gcc --sysroot=$ANDROID_SYSROOT -pie -fPIE"
    export CXX="${CROSS_COMPILER_PREFIX}g++ --sysroot=$ANDROID_SYSROOT -pie -fPIE"
    export CPP="${CROSS_COMPILER_PREFIX}gcc -E --sysroot=$ANDROID_SYSROOT -pie -fPIE"
    export AS="${CROSS_COMPILER_PREFIX}as"
    export LD="${CROSS_COMPILER_PREFIX}ld --sysroot=$ANDROID_SYSROOT -pie -fPIE"
    export GDB="${CROSS_COMPILER_PREFIX}gdb"
    export STRIP="${CROSS_COMPILER_PREFIX}strip"
    export RANLIB="${CROSS_COMPILER_PREFIX}ranlib"
    export OBJCOPY="${CROSS_COMPILER_PREFIX}objcopy"
    export OBJDUMP="${CROSS_COMPILER_PREFIX}objdump"
    export AR="${CROSS_COMPILER_PREFIX}ar"
    export NM="${CROSS_COMPILER_PREFIX}nm"
    export READELF="${CROSS_COMPILER_PREFIX}readelf"
    export M4=m4
    export TARGET_PREFIX=$CROSS_COMPILER_PREFIX
    export CONFIG_SITE="config.site"

    # In python 3.7.1, we must define "__ANDROID_API__", we set the value=21
    export CFLAGS="-D__ANDROID_API__=21"
    export CXXFLAGS="-D__ANDROID_API__=21"
    export CPPFLAGS="-D__ANDROID_API__=21"
    # export STRIP="${CROSS_COMPILER_PREFIX}strip --strip-unneeded"
    
    mkdir -p build/android
    cd build/android

    echo -e "ac_cv_file__dev_ptmx=yes\nac_cv_file__dev_ptc=no" > config.site
 
   # 64位时--host要改成aarch64-linux,prefix和上面的builddir随便
   ../../configure LDFLAGS="-Wl,--allow-shlib-undefined" --host=arm-linux --build=x86_64-linux-gnu --enable-shared --prefix=$WORKSPACE/out/android --enable-ipv6
  • 编译过程中几乎只有一处c代码报错:
../Modules/posixmodule.c:6903:9: error: implicit declaration of function 'wait3' [-Werror=implicit-function-declaration]
        pid = wait3(&status, options, &ru);
        ^
进入代码,补充wait3的定义。(因为ndk提供的接口中没有公开wait3函数调用,但是安卓的bionic libc中有包含这个调用)
#if defined(HAVE_SYS_RESOURCE_H)
#include /resource.h>
#endif
+#ifdef __ANDROID_API__
+
+# ifndef wait3
+ extern pid_t wait3(WAIT_TYPE *status, int option, struct rusage *usage);
+# endif
+#endif+
  • 继续编译python,编译完成时会给出已经编译的模块(extension)和未编译的模块。
INFO: Could not locate ffi libs and/or headers

Python build finished successfully!
The necessary bits to build these optional modules were not found:
_bz2                 _curses              _curses_panel     
_dbm                 _gdbm                _hashlib          
_lzma                _sqlite3             _ssl              
_tkinter             _uuid                nis               
readline             spwd                                    
To find the necessary bits, look in setup.py in detect_modules() for the module's name.


The following modules found by detect_modules() in setup.py, have been
built by the Makefile instead, as configured by the Setup files:
_abc                 atexit               pwd               
time                                                          


Failed to build these modules:
_crypt               _ctypes                                 


Could not build the ssl module!
Python requires an OpenSSL 1.0.2 or 1.1 compatible libssl with X509_VERIFY_PARAM_set1_host().
LibreSSL 2.6.4 and earlier do not provide the necessary APIs, https://github.com/libressl-portable/portable/issues/381
  • make install,将生成的bin目录合并至安卓设备的/system/bin下,将lib目录合并至设备的/system/lib下。执行效果:
130|(略):/ # pyth python3 python3.7 python3.7m python3-config python3.7-config python3.7m-config 130|(略):/ # python3 Python 3.7.1 (default, Nov 6 2018, 20:11:05) [GCC 4.9 20140827 (prerelease)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import base64 >>> base64.b64encode(b'abcd') b'YWJjZA==' >>> base64.b64decode(b'YWJjZA==') b'abcd' >>> (略):/ #

注意:

根据编译python3.5.6的经验:
 
Ubuntu 16.04安装的版本为python3.5.2,相比于3.5.6版本,其主程序在weakref.py/_weakref.c中少了_remove_dead_weakref()函数等。因此python在交叉编译,使用系统自带python3.5执行下列命令的过程中,会出现找不到函数的异常:
 
$ _PYTHON_PROJECT_BASE=/home/xys/projects/Python-3.5.6/build.android _PYTHON_HOST_PLATFORM=linux-arm PYTHONPATH=../Lib:../Lib/plat-linux python3.5 -S -m sysconfig --generate-posix-vars
Could not import runpy module
Traceback (most recent call last):
  File "../Lib/runpy.py", line 14, in
    import importlib.machinery # importlib first so we can test #15386 via -m
  File "../Lib/importlib/__init__.py", line 57, in
    import types
  File "../Lib/types.py", line 166, in
    import functools as _functools
  File "../Lib/functools.py", line 23, in
    from weakref import WeakKeyDictionary
  File "../Lib/weakref.py", line 12, in
    from _weakref import (
ImportError: cannot import name '_remove_dead_weakref'
 
因此,交叉编译Python的时候最好保证电脑上的Python版本和要编译的Python版本一致,即先编译安装Linux版本,再编译arm版本。

已查阅得知的其他方案

这种方案采用crystax ndk (1)( BSD 2-clause license协议),编译时在安卓app项目中嵌入libpython3.5m.so,从而实现Android Java ↔ JNI ↔ Python的相互调用。

这种方案采用 CLE(Common Language Extension)提供的解决方案,同样在安卓app项目中嵌入libpython3.5m.so,从而实现Android Java ↔ JNI ↔ Python的相互调用。

资料

  1. https://www.crystax.net/android/ndk CrystaX NDK,据说在Android NDK的基础上做了很多改进
  2. https://github.com/joaoventura/pybridge joaoventura/pybridge: Reuse Python code in native Android applications
  3. http://www.srplab.com/cn/index.html CLE方案 用中间件的方式在不同平台执行脚本
  4. https://kivy.org/#home Kivy: Cross-platform Python Framework for NUI Development,用kv这个东西管理界面,用Python代码完成逻辑,从而让Python在各个平台上运行。
分享:
 
 
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部