MicroPython:Основы/Язык MicroPython и его реализация/Дистрибутивы, управление пакетами и развертка приложений
Дистрибутивы, управление пакетами и развертка приложений[1]
Как и в «старшем» Python, в MicroPython можно создавать «сторонние» пакеты, распространять их и легко устанавливать на платформу каждого пользователя. Здесь рассказывается, как все это делать, но подразумевается, что у вас уже есть некоторые знания о работе с пакетами в Python.
Процесс создания и использования пакетов включает в себя следующие этапы:
- Модули и пакеты Python преобразовываются в дистрибутивные пакеты-архивы и публикуются в каталоге Python Package Index («каталог пакетов Python») или PyPI.
- Для установки дистрибутивного пакета (дистрибутива) в MicroPython-порт, у которого есть средства для выхода в сеть (например, Unix), можно воспользоваться менеджером пакетов upip.
- Для установки дистрибутива в MicroPython-порт, у которого нет средства для выхода в сеть, можно сначала подготовить «установочный образ» на Unix-порте, а затем с помощью подходящих коммуникационных средств передать его на устройство.
- Если речь об устройства с ограниченной памятью, установочный образ можно заморозить как байт-код в прошивку MicroPython, что позволит минимизировать требования к памяти.
Ниже все эти шаги описаны более подробно.
Распространение пакетов
Модули и пакеты Python можно упаковать в архивы, которые затем можно передавать из одной системы в другую, сохранять в общеизвестном хранилище (PyPl) и при необходимости оттуда их скачивать (чтобы потом установить). Эти архивы также называются «дистрибутивами» (что отличает их от Python-пакетов, предназначенных для организации исходного кода Python).
Форматом для дистрибутива MicroPython является широко известный «tar.gz», но с некоторыми адаптациями. В компрессоре GZIP для работы с TAR-архивами используется внешняя утилита, которая по умолчанию использует размер словаря в 32 Кб, и это значит, что при распаковке сжатого потокового объекта понадобятся смежные участки памяти общим размером в 32 Кб. На устройствах с ограниченной памятью такой вариант может быть неприемлем, потому что вся их память может быть меньше, чем эти 32 Кб, но даже если больше, из-за фрагментации такой большой блок смежных участков памяти, возможно, найти будет трудно. Для решения этой проблемы в дистрибутивах MicroPython используется GZIP-сжатие с размером словаря в 4 Кб, что создает приемлемый компромисс между возможностью более-менее сжать данные и возможностью распаковать их даже на самых маленьких устройствах.
Помимо маленького размера словаря при сжатии, в дистрибутивах MicroPython есть и другие оптимизационные функции – вроде удаления из архива всех файлов, не используемых в процессе установки. В частности, во время установки менеджер пакетов upip не выполняет файл «setup.py» (см. выше), и поэтому этот файл в архив добавлен не будет.
В то же время эти оптимизационные меры делают дистрибутивы MicroPython не совместимыми с пакетным менеджером CPython – pip. Но это не такая уж большая проблема, потому что пакеты можно установить с помощью upip, а потом использовать с помощью CPython (если они совместимы). С другой стороны, большинство пакетов CPython по разным причинам не совместимы с MicroPython, в первую очередь – потому что полагаются на функции, которые не реализованы в MicroPython.
Итак, если вкратце, дистрибутивы-архивы MicroPython хорошо оптимизированы под целевые платформы MicroPython, коими являются устройства с ограниченными ресурсами.
Менеджер пакетов upip
Дистрибутивы MicroPython необходимо устанавливать с помощью менеджера пакетов upip. Это Python-приложение, которое обычно распространяется (в виде замороженного байт-кода) вместе с портами MicroPython, имеющими средства для работы с сетью. Менеджер пакетов upip доступен как минимум на порте MicroPython Unix.
На любом MicroPython-порте, где есть upip, доступ к нему можно получить следующим образом:
import upip
upip.help()
upip.install(package_or_package_list, [path])
Здесь package_or_package_list – это название дистрибутива или список с названиями дистрибутивов, которые нужно установить. В опциональном параметре path указывается место в файловой системе, куда его/их надо установить. По умолчанию установка выполняется в стандартную директорию с библиотеками (см. ниже).
Ниже – пример установки пакета и его последующего использования:
>>> import upip
>>> upip.install("micropython-pystone_lowmem")
[...]
>>> import pystone_lowmem
>>> pystone_lowmem.main()
Обратите внимание, что названия Python-пакета и дистрибутива, как правило, совпадать не должны, и зачастую действительно не совпадают. Дело в том, что PyPI – это центральный репозиторий пакетов для всего разнообразия реализаций и версий Python, и поэтому ваш дистрибутив, возможно, надо будет поместить в пространство имен конкретной реализации. К примеру, названия всех пакетов в «micropython-lib» следуют следующему правилу: если модуль или пакет Python назван foo, то его дистрибутив будет называться micropython-foo.
В портах, где прошивка MicroPython запускается из командной оболочки ОС (вроде Unix-порта), upip можно запустить (и обычно именно так он и запускается) именно оттуда, а не из MicroPython’овского терминала REPL. Вот команды, соответствующие фрагменту выше:
micropython -m upip -h
micropython -m upip install [-p <path>] <packages>...
micropython -m upip install micropython-pystone_lowmem
Кросс-установка пакетов
Для MicroPython-портов, не имеющих средства для работы с сетью, рекомендуется «кросс-установить» их в «установочный образ» при помощи порта MicroPython Unix, а затем передать этот образ на устройство с помощью подходящих коммуникационных средств.
Для установки в установочный образ в upip нужно воспользоваться переключателем -p:
micropython -m upip install -p install_dir micropython-pystone_lowmem
После того, как эта команда будет выполнена, содержимое пакета (и всех зависимых пакетов) можно будет найти в поддиректории install_dir/. Вам нужно будет отправить содержимое этой директории (без префикса install_dir/) на необходимое место в устройстве – туда, где его сможет найти Python-оператор import (см. раздел «Менеджер пакетов upip» выше).
Кросс-установка пакетов с заморозкой
Для устройств с ограниченной памятью вариант установки в предыдущем разделе не слишком эффективен с точки зрения использования памяти, потому что в нем пакеты устанавливаются в виде исходного кода, поэтому при каждом импорте их надо будет компилировать в байт-код. Эта компиляция требует RAM-памяти, и получившийся байт-код тоже будет храниться в RAM-памяти, что уменьшит количество памяти для хранения данных приложения. Более того, процесс установки выше требует наличия файловой системы на устройстве, а у большинства устройств с ограниченной памятью ее может даже не быть.
Заморозка байт-кода – это процесс, позволяющий решить все описанные выше проблемы, потому что:
- Исходный код предкомпилируется в байт-код и будет сохранен в таком виде
- Байт-код хранится в ROM-, а не в RAM-памяти
- Замороженным пакетам файловая система не нужна
Использование замороженного байт-кода требует создания прошивки MicroPython-порта из исходного кода на Си. Соответственно, процесс будет таким:
- Следуйте инструкциям для своей платформы по настройке тулчейна и сборке порта. К примеру, инструкции для ESP8266-порта находятся в «ports/esp8266/README.md». Перед тем, как приступать к следующим шагам, убедитесь, что сборка порта и установка получившейся прошивки прошли успешно.
- Соберите порт MicroPython Unix, и убедитесь, что он находится в PATH, а затем выполните micropython.
- Переключитесь на директорию порта (например, у ESP8266 это «ports/esp8266/»).
- Запустите make clean-frozen. Это удалит все предыдущие модули, которые были установлены для заморозки (если вы хотите добавить новые модули, этот шаг надо пропустить, чтобы не начинать с нуля).
- Запустите micropython -m upip install -p modules <пакеты>..., чтобы установить пакеты, которые вы хотите заморозить.
- Запустите make clean.
- Запустите make.
После этого вы должны получить прошивку, внутри которой будут модули в виде замороженного байт-кода, которую можно устанавливать как обычно. Пара примечаний:
- Шаг 5 в инструкции выше подразумевает, что используемый дистрибутив есть в каталоге PyPI. Если его там нет, вам надо будет вручную скопировать файлы исходного кода Python в поддиректорию «modules/» в директории порта. (Учтите, что upip не поддерживает установку из репозиториев системы управления версиями).
- У прошивки для «голого железа» обычно имеются ограничения по размеру, поэтому слишком большое количество замороженных модулей может ее переполнить. Обычно в таком случае выдается ошибка связывания. Но иногда сгенерированный образ запустить не получается – это типовой баг, о нем нужно сообщить разработчикам, чтобы они разобрались в чем дело. Если вы столкнулись с такой проблемой, первым делом можно попробовать уменьшить количество замороженных модулей в прошивке.
Создание дистрибутивов
Дистрибутивы для MicroPython создаются также, как и для CPython и любой другой реализации Python (более подробно см. в ссылках в конце статьи). Вместо distutils необходимо использовать setuptools, потому что в distutils не поддерживаются зависимости и другие функции. Для упаковки используется формат sdist (от англ. «source distribution»). Постобработка, о которой рассказывалось выше (и предобработка, о которой будет рассказано в следующем разделе) выполняется при помощи модифицированной setuptools-команды sdist. То есть упаковка будет работать, как и у стандартного setuptools, но пользователю лишь нужно переопределить команду sdist, вставив в setup() вот такой аргумент:
from setuptools import setup
import sdist_upip
setup(
...,
cmdclass={'sdist': sdist_upip.sdist}
)
Модуль sdist_upip.py из фрагмента выше можно найти в micropython-lib.
Ресурсы приложения
Помимо исходного кода, в готовом приложении также часто содержатся файлы данных: шаблоны веб-страниц, изображения и т.д. Понятно, как работать с ними, если приложение установлено вручную – вы просто помещаете эти данные в какое-то место файловой системы, а затем используете обычные функции для доступа к данным.
Но все меняется, когда приложение устанавливается из пакета. Это более продвинутый, простой и гибкий способ установки, но в этом случае вам также нужно воспользоваться более продвинутым методом для доступа к файлам данных. Он заключается в том, чтобы относиться к этим файлам данных как к «ресурсам» и сделать парочку дополнительных манипуляций.
В Python доступ к ресурсам поддерживается с помощью модуля pkg_resources из библиотеки «setuptools». MicroPython тоже частично поддерживает функционал этого модуля – в частности, за это отвечает функция pkg_resources.resource_stream(пакет, ресурс). Идея в том, что приложение может вызвать эту функцию и задать в ней аргументом идентификатор нужного ресурса – это будет относительный путь к файлу данных внутри заданного пакета (как правило, пакета приложения высокого уровня). Она вернет потоковый объект, который можно использовать для доступа к содержимому ресурсов. Таким образом, функция resource_stream() эмулирует интерфейс стандартной функции open().
Если пакет установлен в файловую систему, то resourse_stream() будет использовать файловые операции. Но эта функция может работать и без файловой системы – например, если пакет заморожен в виде байт-кода. Это, однако, требует дополнительного этапа при упаковке приложения – создания «ресурсного Python-модуля».
Идея этого модуля в том, чтобы преобразовать двоичные данные в Python-объект bytes, а затем поместить его в словарь, данные в котором будут упорядочены по названиям ресурсов. Это преобразование выполняется автоматически при помощи переопределенной команды sdist, описанной в предыдущем разделе.
Теперь давайте рассмотрим всё это с помощью наглядного примера. Допустим, ваше приложение имеет следующую структуру:
my_app/
__main__.py
utils.py
data/
page.html
image.png
Файлы «__main__.py» и «utils.py» получают доступ к ресурсам с помощью вот этих функций:
import pkg_resources
pkg_resources.resource_stream(__name__, "data/page.html")
pkg_resources.resource_stream(__name__, "data/image.png")
Вы можете как обычно разрабатывать и делать отладку приложения на порте MicroPython Unix. Когда наступит время делать из него дистрибутив, просто воспользуйтесь переопределенной командой sdist из модуля sdist_upip, как описывалось в разделе выше.
Это создаст ресурсный Python-модуль под названием R.py на основе файлов, объявленных в файлах MANIFEST и MANIFEST.in (любой файл без «*.py» будет считаться ресурсом и добавлен в R.py) – перед тем, как приступить к обычным этапам упаковки.
Если подготовить приложение таким образом, оно будет работать и в виде замороженного байт-кода, и будучи установленным в файловую систему.
Чтобы начать отладку создания R.py, запустите следующее:
python3 setup.py sdist --manifest-only
В качестве альтернативы можно воспользоваться скриптом «tools/mpy_bin2res.py» из дистрибутива MicroPython – в этом случае вам нужно будет также указать пути ко всем файлам ресурсов:
mpy_bin2res.py data/page.html data/image.png