要想将Python解释器移植到Android平台,首先要做的就是将Python源码用NDK工具交叉编译为Android平台的二进制库。目前官方是没有提供对Android平台的支持的,但新的版本已经在考虑对Android提供支持,参考文档 API 24 is the first version where the adb shell is run on the emulator as a shell user instead of the root user previously, and the first version that supports arm64.
Android不是常见的Linux系统,只有Linux内核是共享的,其他一切都是不同的,它使用的C标准库是Bionic,与glibc有很大差异,因此直接使用NDK编译源码会报错。
上一篇博客已经谈到了关于Python第三方库移植的问题,但是 CrytaxNDK中的Python解释器存在一些问题,未支持SSL,导致无法访问HTTPS,这次我们先使用CrytaxNDK重新编译Python
在Linux平台交叉编译Python
首先需要搭建一个Linux环境,推荐使用Ubuntu,这里关于虚拟机安装Ubuntu就不说了
步骤一
设置环境。下载CrytaxNDK的Linux版本,并解压到某个目录。需要说明一下,这里可以不用将CrytaxNDK加入到环境变量,也可以加入,方便以后开发使用
输入命令,配置环境变量
sudo vim /etc/profile
将以下内容添加到文件末尾,其中NDK_DIR指向解压后的CrytaxNDK根目录,请替换成自己的实际目录
export NDK_DIR=/home/CrystaX/crystax-ndk-10.3.2
export PATH=$PATH:$NDK_DIR
export NDK_MODULE_PATH=$NDK_DIR/sources
完成后,输入命令source /etc/profile
更新环境变量,使之生效。
步骤二
下载源码。到官网下载Python3.5的源码,建议下载一个稳定版。将下载之后的源码解压到系统的某个目录,并设置一个全局环境变量,指向Python源码根目录。
export PYTHON_DIR=/home/Python-3.5.1
步骤三
下载OpenSSL源码。由于CrytaxNDK没有带openssl,所以我们必须手动添加。这里我们可以取个巧,直接下载别人编译好的so,懒得去搞环境自己编译。下载地址 这个里面倒是提供了二进制so,但是没提供头文件,所以还是得下载OpenSSL源码
按照下图路径,放入CrytaxNDK根目录下面的sources/openssl/1.0.1p/路径下,头文件放入include中,so放入libs中的对应的CPU架构目录下
步骤四
执行编译。进入到CrytaxNDK根目录下面的build/tools/目录下,执行命令
./build-target-python.sh --ndk_dir=$NDK_DIR --abis=armeabi -j5 --verbose $PYTHON_DIR
实际上CrytaxNDK已经提供了一个简单的编译脚本,可以输入./build-target-python.sh --help
查看一下选项
Valid options (defaults are in brackets):
--help Print this help.
--verbose Enable verbose mode.
--dryrun Set to dryrun mode.
--package-dir=<path> Put prebuilt tarballs into <path>
--ndk-dir=<path> Specify NDK root path for the build
--build-dir=<path> Specify temporary build dir
--abis=<list> Specify list of target ABIs [armeabi armeabi-v7a x86 mips armeabi-v7a-hard arm64-v8a x86_64 mips64]
-j<number> Use <number> parallel build jobs
编译完成之后,即在crystax-ndk-10.3.2/sources/python/3.5/下生成so和相关标准库,看一下modules目录,已经产生了_ssl.so,按照上篇博客,添加_ssl.so,即可愉快的访问HTTPS了。在本博客的最后,我会添加已经编译好的so,但是所有编译的so都未进行回归测试,不保证支持所有的Python特性和库调用。
在Windows平台上编译
使用CrytaxNDK编译Python是比较容易的,因为 CrystaX NDK更接近普通的Linux glibc。实际上不是很推荐在Windows上编译,并不是不能编译,因为交叉编译和你所在的平台并没有太大关系,只要有交叉编译器。我们使用NDK 虽然和编译平台关系不大,但是却和项目构建工具密切相关,因此使用Windows编译,需要额外的做更多事。Python项目只提供了autoconf 和VS的项目文件,而autoconf /automake实际上是执行一系列的shell命令,最终生成一个Makefile文件,最终仍然是使用make工具进行编译。而我们想要在Windows上编译,更为可行的办法是使用Google提供的改进后的Android.mk脚本。不推荐使用通过在Windows上安装模拟shell命令环境的工具实现编译。
现在简单说一下编译方案,我们可以根据build-target-python.sh脚本,改写出Android.mk,然后使用ndk-build命令进行编译即可。
以下是我用来编译Python core 的 Android.mk,其中将MY_PYTHON_SRC_ROOT变量设置为你本地的Python3.5的源码根路径,建议写绝对路径。我在其中定义了几个空的宏,DPREFIX、DEXEC_PREFIX、DVPATH、DPYTHONPATH等,否则会报错。这几个宏是用来设置Python默认的一些加载路径的,但是在嵌入解释器的时候,则无意义。在源码getpath.c中已经进行了注释说明,因此我们在使用之前,一定要先调用Py_SetPath设置加载路径。
LOCAL_PATH := $(call my-dir)
MY_PYTHON_SRC_ROOT := E:/PythonBuild/py35/src
include $(CLEAR_VARS)
LOCAL_MODULE := python3.5m
LOCAL_C_INCLUDES := $(MY_PYTHON_SRC_ROOT)/Include
LOCAL_CFLAGS := -DSOABI=\"cpython-3.5m\" -DPy_BUILD_CORE -DPy_ENABLE_SHARED -DPREFIX=\"\" -DEXEC_PREFIX=\"\" -DVPATH=\"\" -DPYTHONPATH=\"\" -DVERSION=\"3.5.1\"
LOCAL_LDLIBS := -lz
LOCAL_SRC_FILES := ../config.c \
\
$(MY_PYTHON_SRC_ROOT)/Python/random.c \
$(MY_PYTHON_SRC_ROOT)/Python/_warnings.c \
$(MY_PYTHON_SRC_ROOT)/Python/asdl.c \
$(MY_PYTHON_SRC_ROOT)/Python/ast.c \
$(MY_PYTHON_SRC_ROOT)/Python/bltinmodule.c \
$(MY_PYTHON_SRC_ROOT)/Python/ceval.c \
$(MY_PYTHON_SRC_ROOT)/Python/codecs.c \
$(MY_PYTHON_SRC_ROOT)/Python/compile.c \
$(MY_PYTHON_SRC_ROOT)/Python/dynamic_annotations.c \
$(MY_PYTHON_SRC_ROOT)/Python/dynload_shlib.c \
$(MY_PYTHON_SRC_ROOT)/Python/errors.c \
$(MY_PYTHON_SRC_ROOT)/Python/fileutils.c \
$(MY_PYTHON_SRC_ROOT)/Python/formatter_unicode.c \
$(MY_PYTHON_SRC_ROOT)/Python/frozen.c \
$(MY_PYTHON_SRC_ROOT)/Python/future.c \
$(MY_PYTHON_SRC_ROOT)/Python/getargs.c \
$(MY_PYTHON_SRC_ROOT)/Python/getcompiler.c \
$(MY_PYTHON_SRC_ROOT)/Python/getcopyright.c \
$(MY_PYTHON_SRC_ROOT)/Python/getopt.c \
$(MY_PYTHON_SRC_ROOT)/Python/getplatform.c \
$(MY_PYTHON_SRC_ROOT)/Python/getversion.c \
$(MY_PYTHON_SRC_ROOT)/Python/graminit.c \
$(MY_PYTHON_SRC_ROOT)/Python/import.c \
$(MY_PYTHON_SRC_ROOT)/Python/importdl.c \
$(MY_PYTHON_SRC_ROOT)/Python/marshal.c \
$(MY_PYTHON_SRC_ROOT)/Python/modsupport.c \
$(MY_PYTHON_SRC_ROOT)/Python/mysnprintf.c \
$(MY_PYTHON_SRC_ROOT)/Python/mystrtoul.c \
$(MY_PYTHON_SRC_ROOT)/Python/peephole.c \
$(MY_PYTHON_SRC_ROOT)/Python/pyarena.c \
$(MY_PYTHON_SRC_ROOT)/Python/pyctype.c \
$(MY_PYTHON_SRC_ROOT)/Python/pyfpe.c \
$(MY_PYTHON_SRC_ROOT)/Python/pyhash.c \
$(MY_PYTHON_SRC_ROOT)/Python/pylifecycle.c \
$(MY_PYTHON_SRC_ROOT)/Python/pymath.c \
$(MY_PYTHON_SRC_ROOT)/Python/pytime.c \
$(MY_PYTHON_SRC_ROOT)/Python/pystate.c \
$(MY_PYTHON_SRC_ROOT)/Python/pystrcmp.c \
$(MY_PYTHON_SRC_ROOT)/Python/pystrhex.c \
$(MY_PYTHON_SRC_ROOT)/Python/pystrtod.c \
$(MY_PYTHON_SRC_ROOT)/Python/dtoa.c \
$(MY_PYTHON_SRC_ROOT)/Python/Python-ast.c \
$(MY_PYTHON_SRC_ROOT)/Python/pythonrun.c \
$(MY_PYTHON_SRC_ROOT)/Python/structmember.c \
$(MY_PYTHON_SRC_ROOT)/Python/symtable.c \
$(MY_PYTHON_SRC_ROOT)/Python/sysmodule.c \
$(MY_PYTHON_SRC_ROOT)/Python/thread.c \
$(MY_PYTHON_SRC_ROOT)/Python/traceback.c \
\
$(MY_PYTHON_SRC_ROOT)/Parser/acceler.c \
$(MY_PYTHON_SRC_ROOT)/Parser/bitset.c \
$(MY_PYTHON_SRC_ROOT)/Parser/firstsets.c \
$(MY_PYTHON_SRC_ROOT)/Parser/grammar.c \
$(MY_PYTHON_SRC_ROOT)/Parser/grammar1.c \
$(MY_PYTHON_SRC_ROOT)/Parser/listnode.c \
$(MY_PYTHON_SRC_ROOT)/Parser/metagrammar.c \
$(MY_PYTHON_SRC_ROOT)/Parser/myreadline.c \
$(MY_PYTHON_SRC_ROOT)/Parser/node.c \
$(MY_PYTHON_SRC_ROOT)/Parser/parser.c \
$(MY_PYTHON_SRC_ROOT)/Parser/parsetok.c \
$(MY_PYTHON_SRC_ROOT)/Parser/tokenizer.c \
\
$(MY_PYTHON_SRC_ROOT)/Objects/abstract.c \
$(MY_PYTHON_SRC_ROOT)/Objects/accu.c \
$(MY_PYTHON_SRC_ROOT)/Objects/boolobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/bytes_methods.c \
$(MY_PYTHON_SRC_ROOT)/Objects/bytearrayobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/bytesobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/capsule.c \
$(MY_PYTHON_SRC_ROOT)/Objects/cellobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/classobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/codeobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/complexobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/descrobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/dictobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/enumobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/exceptions.c \
$(MY_PYTHON_SRC_ROOT)/Objects/fileobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/floatobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/frameobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/funcobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/genobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/iterobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/listobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/longobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/memoryobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/methodobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/moduleobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/namespaceobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/object.c \
$(MY_PYTHON_SRC_ROOT)/Objects/obmalloc.c \
$(MY_PYTHON_SRC_ROOT)/Objects/odictobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/rangeobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/setobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/sliceobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/structseq.c \
$(MY_PYTHON_SRC_ROOT)/Objects/tupleobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/typeobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/unicodectype.c \
$(MY_PYTHON_SRC_ROOT)/Objects/unicodeobject.c \
$(MY_PYTHON_SRC_ROOT)/Objects/weakrefobject.c \
\
$(MY_PYTHON_SRC_ROOT)/Modules/audioop.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_bisectmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_codecsmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_collectionsmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_csv.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_functoolsmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/hashtable.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_heapqmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_json.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_localemodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_lsprof.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_math.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_pickle.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_randommodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_sre.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_struct.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_weakref.c \
$(MY_PYTHON_SRC_ROOT)/Modules/arraymodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/atexitmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/binascii.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cmathmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_datetimemodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/errnomodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/faulthandler.c \
$(MY_PYTHON_SRC_ROOT)/Modules/fcntlmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/gcmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/getpath.c \
$(MY_PYTHON_SRC_ROOT)/Modules/itertoolsmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/main.c \
$(MY_PYTHON_SRC_ROOT)/Modules/mathmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/md5module.c \
$(MY_PYTHON_SRC_ROOT)/Modules/mmapmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_opcode.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_operator.c \
$(MY_PYTHON_SRC_ROOT)/Modules/parsermodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/posixmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_posixsubprocess.c \
$(MY_PYTHON_SRC_ROOT)/Modules/pwdmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/rotatingtree.c \
$(MY_PYTHON_SRC_ROOT)/Modules/sha1module.c \
$(MY_PYTHON_SRC_ROOT)/Modules/sha256module.c \
$(MY_PYTHON_SRC_ROOT)/Modules/sha512module.c \
$(MY_PYTHON_SRC_ROOT)/Modules/signalmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_stat.c \
$(MY_PYTHON_SRC_ROOT)/Modules/symtablemodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_threadmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_tracemalloc.c \
$(MY_PYTHON_SRC_ROOT)/Modules/termios.c \
$(MY_PYTHON_SRC_ROOT)/Modules/timemodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/xxsubtype.c \
$(MY_PYTHON_SRC_ROOT)/Modules/zipimport.c \
$(MY_PYTHON_SRC_ROOT)/Modules/zlibmodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/getbuildinfo.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_cn.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_hk.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_iso2022.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_jp.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_kr.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_tw.c \
$(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/multibytecodec.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/_iomodule.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/textio.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/iobase.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/bufferedio.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/stringio.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/bytesio.c \
$(MY_PYTHON_SRC_ROOT)/Modules/_io/fileio.c
include $(BUILD_SHARED_LIBRARY)
我们创建一个用于编译的目录build,在其中创建jni目录、libs目录,然后添加我们的Android.mk文件,再创建一个Application.mk文件,添加以下内容
APP_PLATFORM = android-21
APP_ABI := armeabi
接下来将crystax-ndk-10.3.2\build\tools\build-target-python\ 目录下 config.c拷贝到jni目录下,可以看到crystax-ndk-10.3.2\build\tools\build-target-python\下的pyconfig.h实际上是个空文件,因此需要将我提供的pyconfig.h拷贝到Python源码根目录下的Include目录中。
最后就是编译了,cd到我们build根目录下,执行ndk-build命令即可生成 libpython3.5m.so,亲测可用。关于Python的modules编译,按照该思路,编写Android.mk脚本即可,至于怎么编写mk文件,慢慢参照 build-target-python.sh,本篇博客不在继续讨论。另外怎么设置Windows上的CrytaxNDK环境省略,参照之前的博客。
最后谈一谈其他的编译Python so的方式,因为QPython早已经成功的在Android上运行了Python,我们完全可以直接参照QPython的方式
我们进入QPython的GitHub QPython
实际上,QPython这里的编译是来自与另一开源的编译Python的项目,而最老的项目出处,应该来自于这个 项目,之后很多人fork,逐渐演化为使用Python脚本交叉编译。
我们在Ubuntu上进行,Mac的我未测试。
首先 下载NDK r16 beta 2,并配置好环境变量$ANDROID_NDK
其次使用git clone https://github.com/qpython-android/qpython3-toolchain.git 将该交叉编译脚本拉下来。
有一点需要注意,使用他的脚本编译Python时,本地Ubuntu上也要先装好对应的Python版本,比如我要编译Python3.6,那么我先要在Ubuntu上装好3.6。另外一个需要注意的就是网要好,最后先配上代理,因为一套编译脚本实际上是用Python写的,会在线下载需要的各种代码,包括Python源码,网不好,下载需要很久。
准备好这些后,还不能在目录下执行make
,我们先进入pybuild目录,编辑一下env.py文件,指定CPU架构,android_api_level
target_arch = 'arm'
android_api_level = 21
这时已经可以愉快的执行make
命令了,然后就是漫长的等待,完成后,进入qpython3-toolchain/src/cpython 下即可找到二进制,唯一比较坑的是,它生成的是libpython3.6m.a的静态库。通过阅读了整个交叉编译脚本后,我发现这套Python脚本只是替换了原来的shell脚本而已,仍然是通过标准的Linux下的安装步骤编译的。即通过执行./configrue
生成Makefile,然后编译。那么我们只需增加一个选项即可编译出动态库。
进入qpython3-toolchain/pybuild/packages/目录下,找到python.py文件,添加如下内容
--enable-shared=yes
修改完保存退出,再次make,就可以愉快的编译出 libpython3.6m.so
最后说一下,编译完之后modules里面的那些so在 qpython3-toolchain/src/cpython/build/lib.linux-arm-3.6 目录下
博客 资源下载链接