Termux — удобная среда для разработки на Android, а сочетание Python, Cython, clang-toolchain и NDK позволяет создавать и отлаживать нативные расширения (.so). В этой статье рассмотрим практический путь: от подготовки окружения до сборки и тестирования нативного модуля Python/Cython внутри Termux.
Фокус материала — на корректной настройке toolchain, выборе ABI, сборке shared library и запуске из Python в Termux.
Что именно мы будем собирать
Цель — собрать динамическую библиотеку .so, которую Python импортирует как расширение модуля. В зависимости от уровня абстракции возможно два подхода:
- Python C-API: ручной модуль на C, компилируемый в .so.
- Cython: написание кода на Cython, генерация C и последующая сборка в .so с помощью clang/NDK.
Далее покажем процесс на примере Cython, а затем — общие принципы для отладки и сборки.
Требования и подготовка окружения Termux
Перед началом убедитесь, что у вас есть:
- Termux (желательно актуальная версия).
- Python в Termux (часто доступен через пакеты Termux).
- clang и связанные инструменты.
- NDK (Android NDK) или компонентная toolchain, позволяющая собирать под нужный ABI.
В Termux установим базовые пакеты. Команды могут отличаться в зависимости от версии репозиториев, но общий подход одинаков:
pkg update
pkg upgrade -y
pkg install -y python python-dev clang make cmake git
Для сборки на стороне Python пригодятся пакеты сборки и Cython:
pip install --upgrade pip setuptools wheel
pip install cython
Важно: на Android версии и настройки ABI (arm64-v8a, armeabi-v7a и т.д.) сильно влияют на правильность сборки. Поэтому определим архитектуру устройства.
Определяем ABI и target
В Termux можно получить информацию об архитектуре через:
uname -m
getprop ro.product.cpu.abi
Типичный маппинг:
- aarch64 →
arm64-v8a - armv7 →
armeabi-v7a
Дальше параметры NDK (например, --target для clang) должны соответствовать ABI.
Размещение и подключение NDK в Termux
Android NDK может быть установлен:
- в виде распакованного архива в файловой системе;
- или через пользовательский workflow/хранилище, где доступен полный путь к папке NDK.
Рекомендуется хранить NDK в доступной директории, затем задавать переменные окружения.
Примерно так (подставьте свой путь):
export ANDROID_NDK_HOME=$HOME/android-ndk
export PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt//bin:$PATH"
Если вы используете shell, где подстановка не срабатывает, укажите точный prebuilt-путь. Для уточнения содержимого:
ls $ANDROID_NDK_HOME/toolchains/llvm/prebuilt
Дальше будет удобнее явно указать prebuilt-версию (например, linux-x86_64 для хоста, либо другой вариант — в зависимости от того, где NDK лежит и как устроен доступ).
Простейший проект на Cython
Создадим рабочую папку и минимальный модуль. Пример: функция вычисляет сумму и показывает, как Cython оформляет модуль.
mkdir -p ~/native_demo/cython_mod
cd ~/native_demo/cython_mod
Файл Cython: cython_mod.pyx:
# cython: boundscheck=False, wraparound=False
# cython: language_level=3
cpdef long add_long(long a, long b):
return a + b
Теперь нам нужен setup.py, который соберёт расширение в .so и положит результат в структуру, пригодную для импорта в Python.
Пример setup.py (концептуальный шаблон; подстройте include/lib пути под свою конфигурацию NDK и версию Python):
import os
import sys
from setuptools import setup
from setuptools.extension import Extension
from Cython.Build import cythonize
# Параметры ABI (обновите под ваше устройство)
ANDROID_ABI = os.environ.get('ANDROID_ABI', 'arm64-v8a')
API_LEVEL = int(os.environ.get('ANDROID_API_LEVEL', '24'))
# Нативный путь к NDK
NDK = os.environ.get('ANDROID_NDK_HOME', os.path.expanduser('~/android-ndk'))
# clang из NDK
# Внимание: prebuilt путь зависит от того, где NDK распакован.
# Обычно используйте: toolchains/llvm/prebuilt/<host_tag>/bin
# Ниже предполагается, что вы уже корректно добавили bin в PATH.
# Подходящий тип таргета clang
# arm64-v8a -> aarch64-linux-android
# armeabi-v7a -> armv7a-linux-androideabi
if ANDROID_ABI == 'arm64-v8a':
target = 'aarch64-linux-android'
else:
target = 'armv7a-linux-androideabi'
# Путь к Python headers в Termux
# На практике путь можно найти через python-config или по директориям sysconfig.
import sysconfig
python_include = sysconfig.get_paths().get('include')
ext_modules = [
Extension(
name='cython_mod',
sources=['cython_mod.pyx'],
language='c',
extra_compile_args=[
f'--target={target}',
f'-DANDROID_API={API_LEVEL}',
'-fPIC',
'-O2',
],
extra_link_args=[
f'--target={target}',
'-shared',
],
include_dirs=[python_include] if python_include else [],
)
]
setup(
name='cython_mod',
ext_modules=cythonize(ext_modules, compiler_directives={
'language_level': '3',
}),
)
Замечание по важному моменту: сборка .so для Android в Termux зависит от того, какой именно Python вы используете (его архитектура и ABI), а также от того, как настроен sysroot/кросс-компиляция. Если Termux работает на том же ABI, что и target-устройство, вы часто можете использовать режим «native build» с правильным clang/NDK окружением. Если же компилируете «кросс», потребуется sysroot и корректные пути к NDK.
Сборка .so
Создадим виртуальное окружение при необходимости, затем соберём расширение.
export ANDROID_ABI=arm64-v8a
export ANDROID_API_LEVEL=24
export ANDROID_NDK_HOME=$HOME/android-ndk
python -V
python setup.py build_ext --inplace
После успешной сборки вы должны увидеть файл вида cython_mod.so в текущей директории или рядом с ней. Проверим:
ls -la
Импорт из Python в Termux
Проверим, что модуль импортируется и функция работает:
python -c "import cython_mod; print(cython_mod.add_long(2, 40))"
Если импорт не проходит, чаще всего проблема в одном из факторов:
- не та архитектура (
arm64-v8avsarmeabi-v7a); - несовпадение API level/символов;
- не найден путь к библиотекам (обычно зависит от того, динамически ли тянутся зависимости);
- неверное имя/расположение .so.
Отладка: сборка с символами и чтение ошибок
Для отладки важно собирать с символами и без агрессивных оптимизаций:
# Вариант: перед сборкой меняем флаги
export CFLAGS="-g -O0"
export ANDROID_NDK_DEBUG=1
python setup.py build_ext --inplace
Типичные способы понять ошибку компиляции/линковки:
- Смотреть полный вывод сборки из
build_ext. - Проверить, что
--targetсоответствует вашему ABI. - Проверить, что подключены include paths Python headers.
- Если ругается на sysroot/standard library — уточнить пути к NDK и наличие нужных библиотек.
Также полезно собрать и изучить, какие зависимости тащит .so:
readelf -d cython_mod.so | head
Если у вас есть ldd (или аналог на Android), используйте его для диагностики. На практике поведение зависит от версии Android и наличия утилит в Termux.
Ускорение итераций и структура проекта
Чтобы меньше ждать, делайте следующее:
- Сохраняйте сборочные артефакты в отдельной папке (например,
build/), но оставляйте--inplaceкак удобный режим для теста. - Минимизируйте число изменений между сборками: сначала добейтесь правильного импорта .so, затем оптимизируйте.
- Используйте кэш зависимостей Python и Cython.
Практическая структура:
native_demo/
cython_mod/
cython_mod.pyx
setup.py
build/
cython_mod.so
Особенности Cython в окружении Termux
Cython может генерировать C-код, который затем компилируется. Для управляемости рекомендуют:
- Ставить
language_level=3. - Убирать «лишние» проверки (например, boundscheck) на этапе экспериментов после валидации корректности.
- Фиксировать версию Cython при работе в команде.
Пример директив в .pyx:
# cython: boundscheck=False, wraparound=False
Вариант: нативный модуль на C (быстрый старт)
Если вы хотите минимальный тест без Cython, можно собрать модуль на C через Python C-API. Концепция одинаковая: вы пишете PyModuleDef и функцию и компилируете в .so.
Пример идеи для module.c (шаблон):
#include <Python.h>
static PyObject add(PyObject self, PyObject args) {
long a, b;
if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
return NULL;
}
return PyLong_FromLong(a + b);
}
static PyMethodDef methods[] = {
{"add", add, METH_VARARGS, "Add two longs."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
"module_name",
NULL,
-1,
methods
};
PyMODINIT_FUNC PyInit_module_name(void) {
return PyModule_Create(&module_def);
}
Дальше вы компилируете его в .so теми же принципами target/ABI/API level и include path на Python headers.
Типичные ошибки и как их диагностировать
- ImportError: wrong ELF class / cannot open shared object file
Обычно означает несоответствие архитектуры (например, попытка загрузить armv7 .so в arm64 процесс) или неверное расположение библиотеки. - Symbol lookup errors
Часто связано с зависимостями или несовместимостью runtime. - Компилятор не находит заголовки Python
Проверьтеpython_includeи что используете headers именно той версии Python, которая установлена в Termux. - Ошибка линковки
Откройте полный лог: обычно там указан отсутствующий символ/библиотека, и по нему можно корректировать линковочные аргументы.
Проверка, что всё собрано под нужный ABI
Убедимся, что ваш .so соответствует архитектуре. В Termux можно использовать readelf (если доступен):
readelf -h cython_mod*.so | grep -E "Machine|Class"
Сравните результат с ожидаемым ABI (arm64 / armv7).
Мини-руководство по отладке процесса сборки
Когда что-то не работает, применяйте «дерево причин»:
- Сборка в Cython проходит ли до конца?
- Получается ли .so?
- Импортируется ли модуль в Python?
- Если нет — какая точная ошибка (текст исключения import/линковки)?
- Сверьте ABI и target-аргументы clang.
Практически полезно временно включать более детальные логи. Например, заставлять build показывать команды компилятора (конкретные флаги зависят от setuptools, но обычно помогает добавление -v на уровне сборки).
Заключение
Разработка нативных модулей Python/Cython в Termux с использованием NDK и clang‑toolchain — достижимая задача при правильной настройке ABI, target/flags, путей к Python headers и контроле процесса сборки до получения корректного .so. Начните с минимального проекта, добейтесь импорта, затем переходите к оптимизации и расширению функциональности.
Если хотите ускорить настройку под вашу версию Android, ABI и конкретный Python в Termux, обращайтесь в РыбинскЛАБ: поможем с подбором toolchain, сборочными скриптами, диагностикой ошибок линковки и запуском нативных расширений на вашем окружении.