Файлы манифеста MicroPython

Краткое описание

В MicroPython есть возможность «замораживать» код Python в прошивке в качестве альтернативы загрузке кода из файловой системы.

Это даёт следующие преимущества:

  • код заранее компилируется в байт-код, что избавляет от необходимости компилировать исходный код Python во время загрузки.

  • байт-код может выполняться непосредственно из ПЗУ (то есть из флеш-памяти), вместо того чтобы копироваться в ОЗУ. Аналогично из ПЗУ загружаются и любые константные объекты (строки, кортежи и т.д.). Это позволяет значительно увеличить объём памяти, доступной вашему приложению.

  • на устройствах без файловой системы это единственный способ загрузить код Python.

Во время разработки замораживание, как правило, не рекомендуется, поскольку оно значительно замедляет цикл разработки: каждое обновление потребует повторной прошивки всего образа прошивки. Тем не менее, бывает полезно выборочно замораживать некоторые редко изменяемые зависимости (например, сторонние библиотеки).

Список файлов Python, которые нужно заморозить в прошивке, задаётся через «манифест» — файл Python, который интерпретируется в процессе сборки. Обычно манифест пишут как часть определения платы, но можно также написать отдельный манифест и использовать его с существующим определением платы.

Файлы манифеста могут определять зависимости от библиотек из micropython-lib, а также от файлов Python в файловой системе и от других файлов манифеста.

Написание файлов манифеста

Файл манифеста — это файл Python, содержащий последовательность вызовов функций. См. доступные функции, описанные ниже.

Любые пути, используемые в файлах манифеста, могут включать следующие переменные. Все они разрешаются в абсолютные пути.

  • $(MPY_DIR) – путь к репозиторию micropython.

  • $(MPY_LIB_DIR) – путь к подмодулю micropython-lib. Предпочтительнее использовать require().

  • $(PORT_DIR) – путь к текущему порту (например, ports/stm32)

  • $(BOARD_DIR) – путь к текущей плате (например, ports/stm32/boards/OPENMV4)

Пользовательские файлы манифеста не должны находиться в основном репозитории MicroPython. Вам следует хранить их в системе контроля версий вместе с остальной частью вашего проекта.

Обычно манифест, используемый для компиляции прошивки, должен включать манифест порта, который может содержать замороженные модули, необходимые для работы платы. Если вы просто хотите добавить дополнительные модули к существующей плате, то включите манифест платы (который, в свою очередь, включит манифест порта).

Сборка с пользовательским манифестом

Ваш манифест можно указать в командной строке make следующим образом:

$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py

Это относится ко всем портам, в том числе основанным на CMake (например, rp2), поскольку обёртка Makefile передаёт это значение в сборку CMake.

Добавление манифеста в определение платы

Если у вас есть пользовательское определение платы, вы можете сделать так, чтобы оно автоматически включало ваш пользовательский манифест. На портах, основанных на make (большинство портов), задайте переменную FROZEN_MANIFEST в файле mpconfigboard.mk.

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

На портах, основанных на CMake (например, rp2), используйте вместо этого mpconfigboard.cmake

set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)

Высокоуровневые функции

Это функции, которые вы обычно будете использовать. Они добавляют код в набор, который заранее компилируется в байт-код и замораживается в образе прошивки:

  • module и package замораживают ваш собственный локальный исходный код — соответственно отдельный файл или целый каталог пакета.

  • require замораживает опубликованный пакет (и его зависимости) из micropython-lib по имени.

  • include подключает другой манифест, так что его замороженные модули тоже добавляются.

  • add_library и metadata — это вспомогательные функции (регистрация дополнительных путей поиска для require и объявление метаданных пакета).

Типичный манифест прошивки сначала includeит манифест порта или платы (чтобы модули, необходимые плате, оставались замороженными), а затем добавляет свои собственные строки module/package/require.

Примечание: для различных функций можно задать именованный аргумент opt, который управляет уровнем оптимизации, используемым кросс-компилятором. См. micropython.opt_level().

add_library(library, library_path, prepend=False)

Регистрирует путь к внешней именованной library.

Используйте это, когда хотите, чтобы require разрешал пакеты из каталога, отличного от micropython-lib — например, из вашей собственной коллекции драйверов или из выгруженной сторонней библиотеки.

Путь library_path будет автоматически просматриваться при использовании require. По умолчанию добавленная библиотека помещается в конец списка библиотек для поиска. Передайте True, чтобы добавить в начало списка.

Кроме того, добавленную библиотеку можно явно запросить с помощью require("name", library="library").

package(package_path, files=None, base_path='.', opt=None)

Замораживает целый пакет — каталог файлов .py (необязательно с вложенными пакетами) — так чтобы его можно было импортировать как import <package>. Для отдельного самостоятельного файла используйте вместо этого module.

Это эквивалентно копированию каталога «package_path» на устройство (за исключением того, что код будет заморожен).

В простейшем случае, чтобы заморозить пакет «foo» в текущем каталоге:

package("foo")

рекурсивно включит все файлы .py в foo и заморозит их как foo/**/*.py.

Если пакет находится не в том же каталоге, что и файл манифеста, используйте base_path:

package("foo", base_path="path/to/libraries")

В base_path можно использовать переменные, приведённые выше, такие как $(PORT_DIR).

Чтобы ограничиться определёнными файлами в пакете, используйте files (примечание: пути должны быть относительными по отношению к пакету): package("foo", files=["bar/baz.py"]).

module(module_path, base_path='.', opt=None)

Замораживает отдельный самостоятельный файл .py, чтобы его можно было импортировать по имени (module("foo.py") делает работающим import foo). Для каталога/пакета используйте package.

Если файл находится в текущем каталоге:

module("foo.py")

В противном случае используйте base_path для указания местоположения файла:

module("foo.py", base_path="src/drivers")

В base_path можно использовать переменные, приведённые выше, такие как $(PORT_DIR).

require(name, library=None)

Требует пакет по имени (и его зависимости) из micropython-lib.

Именно так замораживаются расширения стандартной библиотеки и драйверы сообщества: именованный пакет извлекается из подмодуля micropython-lib и замораживается вместе со всем, от чего он зависит. Чтобы заморозить собственный исходный код, а не опубликованный пакет, используйте вместо этого module или package.

При необходимости укажите library (строку), чтобы сослаться на пакет из библиотеки, которая ранее была зарегистрирована с помощью add_library. В противном случае будет использован список путей к библиотекам.

include(manifest_path)

Включает другой манифест. Именно так составляются манифесты: пользовательский манифест прошивки должен include-ить манифест порта (или платы), чтобы модули, необходимые плате, оставались замороженными, а затем добавлять свои собственные записи.

Обычно манифест, используемый для компиляции прошивки, должен включать манифест порта, который может содержать замороженные модули, необходимые для работы платы.

Аргумент manifest может быть строкой (именем файла) или итерируемым набором строк.

Относительные пути разрешаются относительно текущего файла манифеста.

Если путь указывает на каталог, то неявно включается файл manifest.py внутри этого каталога.

В manifest_path можно использовать переменные, приведённые выше, такие как $(PORT_DIR).

metadata(description=None, version=None, license=None, author=None)

Определяет метаданные для этого файла манифеста. Это полезно для манифестов пакетов micropython-lib.

Эти поля используются при публикации пакета в micropython-lib / установке из него через mip; в манифесте прошивки платы они не нужны.

Низкоуровневые функции

Эти функции задокументированы для полноты, но, за исключением freeze_as_str, ко всем возможностям можно получить доступ через высокоуровневые функции.

Функции freeze* различаются только тем, как хранится код:

  • freeze_as_mpy / freeze_mpy сохраняют предварительно скомпилированный байт-код (.mpy) во флеш-памяти. Код выполняется непосредственно из флеш-памяти, использует минимум ОЗУ и быстро импортируется. Именно это используют внутри себя module, package и require.

  • freeze_as_str вместо этого замораживает исходный код Python, который компилируется в байт-код во время импорта (используя ОЗУ и требуя наличия компилятора на устройстве). Это единственная возможность, не предоставляемая высокоуровневыми функциями, поэтому она и является упомянутым выше исключением.

freeze(path, script=None, opt=0)

Базовый примитив, на котором строятся высокоуровневые функции; предпочитайте именно их. Замораживает входные данные, указанные в path, автоматически определяя их тип. Скрипт .py сначала будет скомпилирован в .mpy, а затем заморожен, а файл .mpy будет заморожен напрямую.

path должен быть каталогом, который является базовым каталогом для начала поиска файлов. При импорте полученных замороженных модулей имя модуля начинается после path, то есть path исключается из имени модуля.

Если path является относительным, он разрешается относительно текущего manifest.py.

Если script равен None, будут заморожены все файлы в path.

Если script является итерируемым набором, то freeze() вызывается для всех его элементов (с теми же path и opt).

Если script является строкой, то она задаёт файл или каталог для замораживания и может включать дополнительные каталоги перед файлом или последним каталогом. Файл или каталог будет искаться в path. Если script является каталогом, то будут заморожены все файлы в этом каталоге.

opt — это уровень оптимизации, передаваемый mpy-cross при компиляции .py в .mpy. Эти уровни описаны в micropython.opt_level().

freeze_as_str(path)

Замораживает заданный path и все скрипты .py внутри него как строку, которая будет скомпилирована при импорте. Используйте это только тогда, когда замороженный код должен оставаться исходным кодом Python; по сравнению с вариантами .mpy это требует ОЗУ во время импорта.

freeze_as_mpy(path, script=None, opt=0)

Замораживает входные данные, сначала компилируя скрипты .py в файлы .mpy, а затем замораживая полученные файлы .mpy. Именно это делают module и package под капотом. Дополнительные сведения об аргументах см. в freeze().

freeze_mpy(path, script=None, opt=0)

Замораживает входные данные, которыми должны быть файлы .mpy, замораживаемые напрямую (без этапа компиляции). Дополнительные сведения об аргументах см. в freeze().

Примеры

Чтобы заморозить отдельный файл из текущего каталога, который будет доступен как import mydriver, используйте:

module("mydriver.py")

Чтобы заморозить каталог файлов в подкаталоге «mydriver» текущего каталога, который будет доступен как import mydriver, используйте:

package("mydriver")

Чтобы заморозить библиотеку «hmac» из micropython-lib, используйте:

require("hmac")

Более полный пример пользовательского файла manifest.py (для платы, имеющей собственный манифест по умолчанию):

# Include the board's default manifest.
include("$(BOARD_DIR)/manifest.py")
# Add a custom driver
module("mydriver.py")
# Add aiorepl from micropython-lib
require("aiorepl")

После этого плату можно скомпилировать с помощью

$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py

Обратите внимание, что у большинства плат нет собственного manifest.py, вместо этого они напрямую используют манифест порта; в таком случае ваш манифест должен просто содержать include("$(PORT_DIR)/boards/manifest.py").