We detected you are likely not from a Russian-speaking region. Would you like to switch to the international version of the site?

  Назад к списку статей

Разработка и отладка нативных модулей Python/Cython в Termux: NDK и clang‑toolchain

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

Типичный маппинг:

  • aarch64arm64-v8a
  • armv7armeabi-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-v8a vs armeabi-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).

Мини-руководство по отладке процесса сборки

Когда что-то не работает, применяйте «дерево причин»:

  1. Сборка в Cython проходит ли до конца?
  2. Получается ли .so?
  3. Импортируется ли модуль в Python?
  4. Если нет — какая точная ошибка (текст исключения import/линковки)?
  5. Сверьте ABI и target-аргументы clang.

Практически полезно временно включать более детальные логи. Например, заставлять build показывать команды компилятора (конкретные флаги зависят от setuptools, но обычно помогает добавление -v на уровне сборки).

Заключение

Разработка нативных модулей Python/Cython в Termux с использованием NDK и clang‑toolchain — достижимая задача при правильной настройке ABI, target/flags, путей к Python headers и контроле процесса сборки до получения корректного .so. Начните с минимального проекта, добейтесь импорта, затем переходите к оптимизации и расширению функциональности.

Если хотите ускорить настройку под вашу версию Android, ABI и конкретный Python в Termux, обращайтесь в РыбинскЛАБ: поможем с подбором toolchain, сборочными скриптами, диагностикой ошибок линковки и запуском нативных расширений на вашем окружении.

* Текст статьи подготовлен и структурирован с использованием технологий искусственного интеллекта. Проверен и доработан перед публикацией.

Нужна помощь с настройкой Termux, Linux и серверов?

Я оказываю ИТ-услуги: настройка серверов, автоматизация, безопасность, помощь с Linux и инфраструктурой. Материалы сайта — только в ознакомительных и образовательных целях.

Связаться со мной
Поддержать проект