1. 程式人生 > >Android 平臺的Python——編譯Python直譯器

Android 平臺的Python——編譯Python直譯器

要想將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 目錄下 在這裡插入圖片描述