尽管已经有了termux,但是还是想要一个更自然的python,然后重复打造轮子吧,
首先感谢yan12125 项目地址 GitHub - yan12125/python3-android: Python 3 cross-compilation tools for Android.
环境和文中有些不一样,但基本都是按他的方法一步步实现的
首先是python3的版本选择问题,我选用了python3.9.2,因为我有一台altas 200i DK,上面内置的是python3.9.2,想运行时少考虑点东西,所以选了3.9.2,这会导致python3-android项目中的补丁文件失效,后面说
首先当然是
git clone https://github.com/yan12125/python3-android.git
1 NDK
根据文档要求,首先要有ndk 我下载的是r19c https://github.com/android/ndk/wiki/Unsupported-Downloads
可能作者和我使用的版本不一样,后期会要求一个不存在的文件llvm-rablib,所以解压后在toolchains/llvm/prebuilt/linux-x86_64/bin 目录下去创建一个软链接即可
ln -s aarch64-linux-android-ranlib llvm-ranlib
2 python3
python3下载地址
Python Source Releases | Python.org
然后修改configure.ac
找到
INSTSONAME="$LDLIBRARY".$SOVERSION
修改为
if test $ac_sys_system != Linux-android
then
INSTSONAME="$LDLIBRARY".$SOVERSION
fi
源码目录Python-3.9.2 放置于src目录下,
3 修改docker-build.sh
注释掉其中的下载ndk部分,因为1中已经完成了
使用sed 命令添加国内源 。不然apt install受不了
#!/bin/bash
set -e
set -x
sed -i 's#http://deb.debian.org#https://mirrors.ustc.edu.cn#g' /etc/apt/sources.list
sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
apt-get update -y
apt-get install -y autoconf automake cmake gawk gettext git gcc make patch pkg-config
export ANDROID_NDK=/android-ndk
#if [ ! -d "$ANDROID_NDK" ] ; then
# # In general we don't want download NDK for every build, but it is simpler to do it here
# # for CI builds
# NDK_VER=r21d
# apt-get install -y wget bsdtar
# wget --no-verbose https://dl.google.com/android/repository/android-ndk-$NDK_VER-linux-x86_64.zip
# bsdtar xf android-ndk-${NDK_VER}-linux-x86_64.zip
# ANDROID_NDK=/android-ndk-$NDK_VER
#fi
cd /python3-android
./build.sh "$@"
4 修改build.sh
主要是注释掉了其中的下载,解压过程和打补丁的过程,因为2中自己做了,ps:还改了版本号
#!/bin/bash
set -e
set -x
THIS_DIR="$PWD"
PYVER=3.9.2
SRCDIR=src/Python-$PYVER
COMMON_ARGS="--arch ${ARCH:-arm64} --api ${ANDROID_API:-28}"
#if [ ! -d $SRCDIR ]; then
# mkdir -p src
# pushd src
#curl -vLO https://www.python.org/ftp/python/$PYVER/Python-$PYVER.tar.xz
# Use --no-same-owner so that files extracted are still owned by the
# running user in a rootless container
#tar --no-same-owner -xf Python-$PYVER.tgz
# popd
#fi
cp -r Android $SRCDIR
pushd $SRCDIR
#patch -Np1 -i ./Android/unversioned-libpython.patch
autoreconf -ifv
python3 ./Android/build_deps.py $COMMON_ARGS
python3 ./Android/configure.py $COMMON_ARGS --prefix=/usr "$@"
make
make install DESTDIR="$THIS_DIR/build"
popd
cp -r $SRCDIR/Android/sysroot/usr/share/terminfo build/usr/share/
cp devscripts/env.sh build/
5 下载deps
在Android目录中创建deps目录,并下载Android/build_deps.py中列明的10个依赖库
因为在docker中访问github.com并下载的困难不用说,直接在主机下载并放在Android/deps中再挂载到docker会方便很多,而且文件上的有些下载地址也失效了,我是手动一个个下的,注意脚本是使用下载地址的文件名来解压的,如果有些压缩格式不一样的,可以在build_deps修改对应的下载地址的文件名。然后注释掉其中的curl部分即可。完整的文件如下
#!/usr/bin/env python3
import shlex
import logging
import os
import re
import subprocess
from typing import List
from util import ARCHITECTURES, BASE, SYSROOT, env_vars, ndk_unified_toolchain, parse_args
logger = logging.getLogger(__name__)
class Package:
def __init__(self, target_arch_name: str, android_api_level: int):
self.target_arch_name = target_arch_name
self.target_arch = ARCHITECTURES[target_arch_name]
self.android_api_level = android_api_level
def run(self, cmd: List[str]):
cwd = BASE / 'deps' / re.sub(r'\.tar\..*', '', os.path.basename(self.source))
logger.debug(f'Running in {cwd}: ' + ' '.join([shlex.quote(str(arg)) for arg in cmd]))
subprocess.check_call(cmd, cwd=cwd)
def build(self):
self.configure()
self.make()
self.make_install()
def configure(self):
self.run([
'./configure',
'--prefix=/usr',
'--libdir=/usr/lib',
'--host=' + self.target_arch.ANDROID_TARGET,
'--disable-shared',
] + getattr(self, 'configure_args', []))
def make(self):
self.run(['make'])
def make_install(self):
self.run(['make', 'install', f'DESTDIR={SYSROOT}'])
class BZip2(Package):
source = 'https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz'
def configure(self):
pass
def make(self):
self.run([
'make', 'libbz2.a',
f'CC={os.environ["CC"]}',
f'CFLAGS={os.environ["CFLAGS"]} {os.environ["CPPFLAGS"]}',
f'AR={os.environ["AR"]}',
f'RANLIB={os.environ["RANLIB"]}',
])
def make_install(self):
# The install target in bzip2's Makefile needs too many fixes -
# Installing files manually is simpler.
self.run(['install', '-Dm644', 'libbz2.a', '-t', str(SYSROOT / 'usr' / 'lib')])
self.run(['install', '-Dm644', 'bzlib.h', '-t', str(SYSROOT / 'usr' / 'include')])
class GDBM(Package):
source = 'https://ftp.gnu.org/gnu/gdbm/gdbm-1.18.1.tar.gz'
configure_args = ['--enable-libgdbm-compat']
class LibFFI(Package):
source = 'https://github.com/libffi/libffi/releases/download/v3.3/libffi-3.3.tar.gz'
# libffi may fail to configure with Docker on WSL2 (#33)
configure_args = ['--disable-builddir']
class LibUUID(Package):
source = 'https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.36/util-linux-2.36.tar.xz'
configure_args = ['--disable-all-programs', '--enable-libuuid']
class NCurses(Package):
source = 'https://invisible-mirror.net/archives/ncurses/ncurses-6.2.tar.gz'
# Not stripping the binaries as there is no easy way to specify the strip program for Android
configure_args = ['--without-ada', '--enable-widec', '--without-debug', '--without-cxx-binding', '--disable-stripping']
class OpenSSL(Package):
source = 'https://www.openssl.org/source/openssl-1.1.1h.tar.gz'
def configure(self):
# OpenSSL handles NDK internal paths by itself
path = os.pathsep.join((
# OpenSSL requires NDK's clang in $PATH to enable usage of clang
str(ndk_unified_toolchain()),
# and it requires unprefixed binutils, too
str(ndk_unified_toolchain().parent / self.target_arch.ANDROID_TARGET / 'bin'),
os.environ['PATH'],
))
logger.debug(f'$PATH for OpenSSL: {path}')
os.environ['PATH'] = path
openssl_target = 'android-' + self.target_arch_name
self.run(['./Configure', '--prefix=/usr', '--openssldir=/etc/ssl', openssl_target,
'no-shared', 'no-tests', f'-D__ANDROID_API__={self.android_api_level}'])
def make_install(self):
self.run(['make', 'install_sw', 'install_ssldirs', f'DESTDIR={SYSROOT}'])
class Readline(Package):
source = 'https://ftp.gnu.org/gnu/readline/readline-8.0.tar.gz'
# See the wcwidth() test in aclocal.m4. Tested on Android 6.0 and it's broken
# XXX: wcwidth() is implemented in [1], which may be in Android P
# Need a conditional configuration then?
# [1] https://android.googlesource.com/platform/bionic/+/c41b560f5f624cbf40febd0a3ec0b2a3f74b8e42
configure_args = ['bash_cv_wcwidth_broken=yes']
class SQLite(Package):
source = 'https://sqlite.org/2020/sqlite-autoconf-3330000.tar.gz'
class XZ(Package):
source = 'https://tukaani.org/xz/xz-5.2.5.tar.gz'
class ZLib(Package):
source = 'https://www.zlib.net/zlib-1.2.11.tar.gz'
def configure(self):
os.environ.update({
'CHOST': self.target_arch.ANDROID_TARGET + '-',
'CFLAGS': ' '.join([os.environ['CPPFLAGS'], os.environ['CFLAGS']]),
})
self.run([
'./configure',
'--prefix=/usr',
'--static',
])
def make(self):
self.run(['make', 'libz.a'])
def build_package(pkg: Package):
#subprocess.check_call(['curl', '-fLO', pkg.source], cwd=BASE / 'deps')
subprocess.check_call(['tar', '--no-same-owner', '-xf', os.path.basename(pkg.source)], cwd=BASE / 'deps')
try:
saved_env = os.environ.copy()
pkg.build()
finally:
os.environ.clear()
os.environ.update(saved_env)
def main():
logging.basicConfig(level=logging.DEBUG)
args, _ = parse_args()
os.environ.update(env_vars(args.target_arch_name, args.android_api_level))
(BASE / 'deps').mkdir(exist_ok=True)
SYSROOT.mkdir(exist_ok=True)
package_classes = (
# ncurses is a dependency of readline
NCurses,
BZip2, GDBM, LibFFI, LibUUID, OpenSSL, Readline, SQLite, XZ, ZLib,
)
for pkg_cls in package_classes:
build_package(pkg_cls(args.target_arch_name, args.android_api_level))
if __name__ == '__main__':
main()
6 开始吧
现在docker中的脚本不再依赖于外网下载了。
docker run --rm -it -v $(pwd):/python3-android \
-v /data/android-ndk-r19c:/android-ndk:ro \
--env ARCH=arm64 --env ANDROID_API=28 \
python:3.9.2-slim \
/python3-android/docker-build.sh
其中的挂载目录依自己的情况而定,python-android对应的是我们工作的主目录,andriod-ndk是第二步中下载的ndk r19c解压目录,我个人的测试机是arm64的 android9机器,所以选择的是 arm64 api level 28,这个可以依个人情况而定
7 enjoy
将上一步生成的build目录下的东西打个包,上传到手机
tar ....
adb push python3.9.tar.gz /data/local/tmp
其实上传到可写目录都行,我看很多朋友都用的这个目录,所以也传到这了
adb shell
cd ..
tar -xvzf ....
cd python3.9
. ./env.sh
adb shell 进手机,到目录,解压。执行env.sh主要是设置环境变量
然后
python3 -V
测试一下