Projeto reprodutível para compilar e empacotar CPython para rodar no shell puro do Android, com foco em Android x86_64 dentro de Redroid, sem Termux, sem APK e sem Gradle.
Este repositório entrega:
- build reproduzível via Docker;
- cross-compile com Android NDK;
- runtime portátil para shell puro do Android;
- variantes
full,minimaleslim; - wrappers globais em
/system/bin/python3,/system/bin/pythone/system/bin/pip; - testes automatizados no Redroid;
- suporte prático a
pippara pacotes pure-Python.
Pacotes adicionais já embutidos no runtime:
fakerrequestsjinja2numpypandasPillowlxmluiautomator2
CPython 3.12.12Android NDK r27dAPI 24- alvo principal:
x86_64 - pronto para expansão para
arm64-v8a,armeabi-v7aex86
O launcher em shell configura:
PYTHONHOMEPYTHONPATHLD_LIBRARY_PATHLD_PRELOADTMPDIRLANGLC_ALLSSL_CERT_FILE
Isso foi necessário para o runtime funcionar de forma confiável no linker do Android.
.
├── .github/workflows/
├── Dockerfile
├── Makefile
├── README.md
├── config/build.env
├── docker-compose.yml
├── output/
├── patches/
├── scripts/
│ ├── build-all.sh
│ ├── build-cpython.sh
│ ├── build-deps.sh
│ ├── build-host-python.sh
│ ├── build-matrix.sh
│ ├── clean.sh
│ ├── export-artifacts.sh
│ ├── package-runtime.sh
│ ├── redroid-enable-pip.sh
│ ├── redroid-pip-install.sh
│ ├── redroid-push.sh
│ ├── redroid-shell.sh
│ ├── redroid-test.sh
│ └── lib/common.sh
└── tests/
├── android_smoke_test.py
├── external_hello.py
└── network_tls_test.py
No host:
- Docker
- Docker Compose v2
- um Redroid já rodando, por exemplo com container
android-15
No Android Redroid:
- shell via
docker exec - diretório gravável em
/data/local/tmp - rede funcional se quiser rodar os testes de TLS/HTTPS
docker compose build builderdocker compose up -d builderdocker compose exec builder bash -lc "./scripts/build-all.sh"Atalho com make:
make compose-build
make compose-up
make buildO build completo também compila as wheels Android nativas de numpy, pandas, Pillow, lxml e uiautomator2 antes de instalar os pacotes no runtime.
Exemplo para arm64-v8a:
docker compose exec builder bash -lc "ANDROID_ABI=arm64-v8a ./scripts/build-all.sh"Atalho equivalente:
ANDROID_ABI=arm64-v8a make buildBuild serial para várias ABIs:
docker compose exec builder bash -lc "./scripts/build-matrix.sh"Depois do build, você terá algo como:
output/dist/python-android-x86_64-full.tar.gzoutput/dist/python-android-x86_64-minimal.tar.gzoutput/dist/python-android-x86_64-slim.tar.gzoutput/dist/python-android-arm64-v8a-full.tar.gzoutput/dist/python-android-arm64-v8a-minimal.tar.gzoutput/dist/python-android-arm64-v8a-slim.tar.gzoutput/dist/SHA256SUMSoutput/dist/ARTIFACTS.txt
Observação: o projeto usa output/ como diretório oficial de saída. Não existe uma pasta out/ neste repositório.
As wheels nativas de numpy, pandas, Pillow, lxml e uiautomator2 ficam em:
output/wheelhouse/x86_64/output/wheelhouse/arm64-v8a/
full: stdlib expandida,ensurepip,venv, headers e arquivos auxiliares.minimal: stdlib majoritariamente zipada, mantémlib-dynload,site-packages,ensurepipevenv.slim: runtime menor, sem headers, sem manpages, sem pkgconfig, sem testes e sem bins auxiliares.
./scripts/redroid-push.shPor padrão, isso:
- copia a variante
minimalpara:
/data/local/tmp/<abi>/python-android-<abi>
- cria wrappers globais em:
/system/bin/python3/system/bin/python/system/bin/pip
Atalho com make:
make redroid-pushPara instalar no Redroid x86_64:
REDROID_CONTAINER=android-15 ANDROID_ABI=x86_64 ./scripts/redroid-push.shPara instalar no Redroid arm64-v8a:
REDROID_CONTAINER=android-15 ANDROID_ABI=arm64-v8a ./scripts/redroid-push.shO deploy usa o tarball correto de output/dist/python-android-<abi>-<variant>.tar.gz, então ele não depende do último ABI empacotado em output/runtime/.
Depois do deploy:
docker exec android-15 sh -lc 'python3 --version'
docker exec android-15 sh -lc 'python -c "import sys; print(sys.version)"'
docker exec android-15 sh -lc 'python3 -c "import numpy, pandas, uiautomator2; from PIL import Image; from lxml import etree; print(numpy.__version__, pandas.__version__, Image.__version__)"'Habilitar e testar pip no Redroid:
REDROID_CONTAINER=android-15 ./scripts/redroid-enable-pip.sh
docker exec android-15 sh -lc 'pip --version'
docker exec android-15 sh -lc 'pip install urllib3'Observação para arm64-v8a: o container Android precisa executar binários ARM64 nativos. Alguns Redroid x86_64 anunciam arm64-v8a em ro.product.cpu.abilist, mas não executam ELF ARM64 standalone no shell.
mkdir -p /tmp/python-android-x86_64
tar -xzf output/dist/python-android-x86_64-minimal.tar.gz -C /tmp/python-android-x86_64
docker exec android-15 sh -lc 'rm -rf /data/local/tmp/x86_64/python-android-x86_64 && mkdir -p /data/local/tmp/x86_64/python-android-x86_64'
docker cp /tmp/python-android-x86_64/. android-15:/data/local/tmp/x86_64/python-android-x86_64
docker exec android-15 sh -lc "cat > /system/bin/python3 <<'EOF'
#!/system/bin/sh
exec /data/local/tmp/x86_64/python-android-x86_64/bin/python3 \"\$@\"
EOF
chmod 0755 /system/bin/python3
ln -sf /system/bin/python3 /system/bin/python
cat > /system/bin/pip <<'EOF'
#!/system/bin/sh
exec /data/local/tmp/x86_64/python-android-x86_64/bin/python3 -m pip \"\$@\"
EOF
chmod 0755 /system/bin/pip"Exemplo para x86_64:
rm -rf /tmp/python-android-x86_64
mkdir -p /tmp/python-android-x86_64
tar -xzf output/dist/python-android-x86_64-minimal.tar.gz -C /tmp/python-android-x86_64
adb shell 'rm -rf /data/local/tmp/x86_64/python-android-x86_64 && mkdir -p /data/local/tmp/x86_64/python-android-x86_64'
adb push /tmp/python-android-x86_64/. /data/local/tmp/x86_64/python-android-x86_64/
adb shell 'cat > /data/local/tmp/python3 <<'\''EOF'\''
#!/system/bin/sh
exec /data/local/tmp/x86_64/python-android-x86_64/bin/python3 "$@"
EOF
chmod 0755 /data/local/tmp/python3
ln -sf /data/local/tmp/python3 /data/local/tmp/python'
adb shell '/data/local/tmp/python3 --version'
adb shell '/data/local/tmp/python3 -c "import numpy, pandas, uiautomator2; from PIL import Image; from lxml import etree; print(numpy.__version__, pandas.__version__, Image.__version__)"'Se o dispositivo tiver root/remount e você quiser wrappers globais:
adb root
adb remount
adb shell 'cat > /system/bin/python3 <<'\''EOF'\''
#!/system/bin/sh
exec /data/local/tmp/x86_64/python-android-x86_64/bin/python3 "$@"
EOF
chmod 0755 /system/bin/python3
ln -sf /system/bin/python3 /system/bin/python
cat > /system/bin/pip <<'\''EOF'\''
#!/system/bin/sh
exec /data/local/tmp/x86_64/python-android-x86_64/bin/python3 -m pip "$@"
EOF
chmod 0755 /system/bin/pip'
adb shell 'python3 --version'Habilitar pip via ADB:
adb shell '/data/local/tmp/x86_64/python-android-x86_64/bin/python3 -m ensurepip --default-pip'
adb shell '/data/local/tmp/x86_64/python-android-x86_64/bin/python3 -m pip --version'
adb shell '/data/local/tmp/x86_64/python-android-x86_64/bin/python3 -m pip install urllib3'Rodar a suíte de validação:
./scripts/redroid-test.shAtalho com make:
make redroid-testO teste valida:
python3 --versionprint("hello from android")import json, ssl, sqlite3, ctypessys.path- criação de arquivo temporário
- execução de script externo
- socket + TLS real
HTTPS GETreal- wrappers em
/system/bin/python3e/system/bin/python - imports de
faker,requests,jinja2,numpy,pandas,Pillow,lxmleuiautomator2
Habilitar pip no runtime:
./scripts/redroid-enable-pip.shTestar instalação de pacote pure-Python:
./scripts/redroid-pip-install.sh urllib3Atalho com make:
make redroid-pipAlém da stdlib principal, o build agora inclui:
fakerrequestsjinja2numpypandasPillowlxmluiautomator2
Dependências puras instaladas junto:
urllib3certifiidnacharset-normalizerMarkupSafepython-dateutilpytztzdata
numpy, pandas, Pillow e lxml têm extensões nativas. uiautomator2 é puro, mas depende de wheels Android nativas como Pillow e lxml, então também é instalado pelo fluxo de pacotes nativos.
O fluxo padrão agora compila essas wheels dentro do builder com crossenv, Meson e o Android NDK:
make native-wheels
make native-packages
make package
make exportNo build completo, essa sequência já é chamada por:
make buildAs wheels geradas ficam em:
output/wheelhouse/<abi>/
Exemplo para x86_64:
output/wheelhouse/x86_64/numpy-...-cp312-cp312-android_24_x86_64.whl
output/wheelhouse/x86_64/pandas-...-cp312-cp312-android_24_x86_64.whl
Exemplo para arm64-v8a:
output/wheelhouse/arm64-v8a/numpy-...-cp312-cp312-android_24_arm64_v8a.whl
output/wheelhouse/arm64-v8a/pandas-...-cp312-cp312-android_24_arm64_v8a.whl
Também é possível sobrescrever:
ANDROID_WHEELHOUSE_DIR: diretório das wheels nativas.ANDROID_WHEEL_PLATFORM_TAG: tag de plataforma usada pelopip, por exemploandroid_24_x86_64.NUMPY_VERSION: versão pinada donumpy.PANDAS_VERSION: versão pinada dopandas.PILLOW_VERSION: versão pinada doPillow.LXML_VERSION: versão pinada dolxml.UIAUTOMATOR2_VERSION: versão pinada douiautomator2.
Se quiser usar wheels próprias em vez de compilar, coloque-as em output/wheelhouse/<abi>/ antes de rodar make native-packages.
O staging gera também:
BUNDLED-PACKAGES.txt
Esse arquivo vai dentro do runtime final e registra as versões empacotadas.
Depois do deploy:
docker exec android-15 sh -lc 'python3 --version'
docker exec android-15 sh -lc 'python --version'
docker exec android-15 sh -lc 'pip --version'
docker exec android-15 sh -lc 'python3 -c "import sys; print(sys.version)"'
docker exec android-15 sh -lc 'python3 -c "import json, ssl, sqlite3, ctypes; print(\"ok\")"'- Não é binário único/estático.
- O formato robusto é launcher +
libpython+ stdlib +lib-dynload. pipno Android foi validado para pacotes pure-Python.- Pacotes com dependências nativas podem exigir cross-build adicional.
readline,curses,tkinter,dbm/gdbme afins não fazem parte do perfil atual.
Limpar build, staging, runtime e dist:
make cleanLimpeza mais agressiva, incluindo downloads e cache local:
make distcleanWorkflows incluídos:
.github/workflows/build.yml.github/workflows/release.yml
Convenção sugerida para tags:
android-python-py3.12.12-r1
android-python-py3.12.12-r2
make compose-buildmake compose-upmake buildmake redroid-pushmake redroid-testmake redroid-pip