python on android 1 --compile

尽管已经有了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
测试一下

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值