Fichiers manifest de MicroPython¶
Résumé¶
MicroPython propose une fonctionnalité qui permet de « figer » (freeze) du code Python dans le micrologiciel, en alternative au chargement du code depuis le système de fichiers.
Cela présente les avantages suivants :
le code est précompilé en bytecode, ce qui évite d’avoir à compiler les sources Python au moment du chargement.
le bytecode peut être exécuté directement depuis la ROM (c.-à-d. la mémoire flash) plutôt que d’être copié en RAM. De même, tous les objets constants (chaînes, tuples, etc.) sont également chargés depuis la ROM. Cela peut libérer beaucoup plus de mémoire pour votre application.
sur les appareils dépourvus de système de fichiers, c’est la seule façon de charger du code Python.
Pendant le développement, le figement (freezing) n’est généralement pas recommandé car il ralentit considérablement votre cycle de développement, chaque mise à jour nécessitant de reflasher l’intégralité du micrologiciel. Il peut toutefois rester utile de figer sélectivement certaines dépendances qui changent rarement (comme des bibliothèques tierces).
La façon de lister les fichiers Python à figer dans le micrologiciel passe par un « manifest », c’est-à-dire un fichier Python qui sera interprété par le processus de compilation. En général, vous écririez un fichier manifest dans le cadre d’une définition de carte, mais vous pouvez aussi écrire un fichier manifest autonome et l’utiliser avec une définition de carte existante.
Les fichiers manifest peuvent définir des dépendances vis-à-vis de bibliothèques de micropython-lib, ainsi que de fichiers Python du système de fichiers, et également d’autres fichiers manifest.
Écriture de fichiers manifest¶
Un fichier manifest est un fichier Python contenant une série d’appels de fonctions. Voir les fonctions disponibles définies ci-dessous.
Tous les chemins utilisés dans les fichiers manifest peuvent inclure les variables suivantes. Elles se résolvent toutes en chemins absolus.
$(MPY_DIR)– chemin vers le dépôt micropython.$(MPY_LIB_DIR)– chemin vers le sous-module micropython-lib. Préférez utiliserrequire().$(PORT_DIR)– chemin vers le port courant (par ex.ports/stm32)$(BOARD_DIR)– chemin vers la carte courante (par ex.ports/stm32/boards/OPENMV4)
Les fichiers manifest personnalisés ne devraient pas se trouver dans le dépôt principal de MicroPython. Vous devriez les conserver dans le système de gestion de versions avec le reste de votre projet.
En général, un manifest utilisé pour compiler le micrologiciel devra inclure le manifest du port, lequel peut inclure des modules figés requis pour le fonctionnement de la carte. Si vous voulez simplement ajouter des modules supplémentaires à une carte existante, alors incluez le manifest de la carte (qui inclura à son tour le manifest du port).
Compilation avec un manifest personnalisé¶
Votre manifest peut être spécifié sur la ligne de commande make avec :
$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py
Cela s’applique à tous les ports, y compris ceux basés sur CMake (par ex. rp2), car le wrapper Makefile transmettra cette valeur à la compilation CMake.
Ajout d’un manifest à une définition de carte¶
Si vous disposez d’une définition de carte personnalisée, vous pouvez faire en sorte qu’elle inclue automatiquement votre manifest personnalisé. Sur les ports basés sur make (la plupart des ports), définissez la variable FROZEN_MANIFEST dans votre mpconfigboard.mk.
FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py
Sur les ports basés sur CMake (par ex. rp2), utilisez plutôt mpconfigboard.cmake
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
Fonctions de haut niveau¶
Ce sont les fonctions que vous utiliserez normalement. Elles ajoutent du code à l’ensemble qui est précompilé en bytecode et figé dans l’image du micrologiciel :
moduleetpackagefigent vos sources locales personnelles — respectivement un fichier unique ou un répertoire de package entier.requirefige un package publié (et ses dépendances) de micropython-lib, par son nom.includeintègre un autre manifest afin que ses modules figés soient eux aussi ajoutés.add_libraryetmetadatasont des fonctions de support (enregistrement de chemins de recherche supplémentaires pourrequire, et déclaration des métadonnées de package).
Un manifest de micrologiciel typique commence par includer le manifest du port ou de la carte (afin que les modules dont la carte a besoin restent figés), puis ajoute ses propres lignes module/package/require.
Note : L’argument nommé opt peut être défini sur les différentes fonctions ; il contrôle le niveau d’optimisation utilisé par le compilateur croisé. Voir micropython.opt_level().
- add_library(library, library_path, prepend=False)¶
Enregistre le chemin vers une library externe nommée.
Utilisez ceci lorsque vous voulez que
requirerésolve les packages depuis un répertoire autre que micropython-lib — par exemple votre propre collection de pilotes, ou une copie de travail d’une bibliothèque tierce.Le chemin library_path sera automatiquement recherché lors de l’utilisation de
require. Par défaut, la bibliothèque ajoutée est placée à la fin de la liste des bibliothèques à parcourir. PassezTruepour préfixer, c’est-à-dire l’ajouter au début de la liste.De plus, la bibliothèque ajoutée peut être demandée explicitement en utilisant
require("name", library="library").
- package(package_path, files=None, base_path='.', opt=None)¶
Fige un package entier — un répertoire de fichiers
.py(éventuellement avec des sous-packages) — afin qu’il puisse être importé avecimport <package>. Utilisez plutôtmodulepour un fichier autonome unique.Cela équivaut à copier le répertoire « package_path » sur l’appareil (sauf qu’il s’agit de code figé).
Dans le cas le plus simple, pour figer un package « foo » dans le répertoire courant :
package("foo")inclura récursivement tous les fichiers .py de foo, qui seront figés sous la forme
foo/**/*.py.Si le package ne se trouve pas dans le même répertoire que le fichier manifest, utilisez
base_path:package("foo", base_path="path/to/libraries")Vous pouvez utiliser les variables ci-dessus, comme
$(PORT_DIR), dansbase_path.Pour restreindre à certains fichiers du package, utilisez
files(note : les chemins doivent être relatifs au package) :package("foo", files=["bar/baz.py"]).
- module(module_path, base_path='.', opt=None)¶
Fige un fichier
.pyautonome unique afin qu’il puisse être importé par son nom (module("foo.py")fait fonctionnerimport foo). Utilisezpackagepour un répertoire/package.Si le fichier est dans le répertoire courant :
module("foo.py")Sinon, utilisez base_path pour localiser le fichier :
module("foo.py", base_path="src/drivers")Vous pouvez utiliser les variables ci-dessus, comme
$(PORT_DIR), dansbase_path.
- require(name, library=None)¶
Requiert un package par son nom (et ses dépendances) depuis micropython-lib.
C’est ainsi que les extensions de la bibliothèque standard et les pilotes communautaires sont figés : le package nommé est récupéré depuis le sous-module micropython-lib et figé avec tout ce dont il dépend. Utilisez plutôt
moduleoupackagepour figer vos propres sources plutôt qu’un package publié.Spécifiez éventuellement library (une chaîne) pour référencer un package d’une bibliothèque préalablement enregistrée avec
add_library. Sinon, la liste des chemins de bibliothèques sera utilisée.
- include(manifest_path)¶
Inclut un autre manifest. C’est ainsi que les manifests sont composés : un manifest de micrologiciel personnalisé devrait
includele manifest du port (ou de la carte) afin que les modules dont la carte a besoin restent figés, puis ajouter ses propres entrées.En général, un manifest utilisé pour compiler le micrologiciel devra inclure le manifest du port, lequel peut inclure des modules figés requis pour le fonctionnement de la carte.
L’argument manifest peut être une chaîne (nom de fichier) ou un itérable de chaînes.
Les chemins relatifs sont résolus par rapport au fichier manifest courant.
Si le chemin pointe vers un répertoire, alors il inclut implicitement le fichier manifest.py situé dans ce répertoire.
Vous pouvez utiliser les variables ci-dessus, comme
$(PORT_DIR), dansmanifest_path.
- metadata(description=None, version=None, license=None, author=None)¶
Définit les métadonnées de ce fichier manifest. Ceci est utile pour les manifests des packages micropython-lib.
Ces champs sont consommés lorsqu’un package est publié sur / installé depuis micropython-lib via mip ; ils ne sont pas nécessaires dans un manifest de micrologiciel de carte.
Fonctions de bas niveau¶
Ces fonctions sont documentées par souci d’exhaustivité, mais à l’exception de freeze_as_str, toutes les fonctionnalités sont accessibles via les fonctions de haut niveau.
Les fonctions freeze* ne diffèrent que par la manière dont le code est stocké :
freeze_as_mpy/freeze_mpystockent du bytecode précompilé (.mpy) en mémoire flash. Le code s’exécute directement depuis la mémoire flash, utilise un minimum de RAM et s’importe rapidement. C’est ce qu’utilisentmodule,packageetrequireen interne.freeze_as_strfige à la place les sources Python, qui sont compilées en bytecode au moment de l’importation (utilisant de la RAM et nécessitant le compilateur embarqué). C’est la seule capacité non exposée par les fonctions de haut niveau, ce qui explique l’exception mentionnée ci-dessus.
- freeze(path, script=None, opt=0)¶
La primitive sous-jacente sur laquelle s’appuient les fonctions de haut niveau ; préférez ces dernières. Fige l’entrée spécifiée par path, en déterminant automatiquement son type. Un script
.pysera d’abord compilé en.mpypuis figé, et un fichier.mpysera figé directement.path doit être un répertoire, qui est le répertoire de base à partir duquel commencer la recherche de fichiers. Lors de l’importation des modules figés résultants, le nom du module commence après path, c’est-à-dire que path est exclu du nom du module.
Si path est relatif, il est résolu par rapport au
manifest.pycourant.Si script vaut None, tous les fichiers de path seront figés.
Si script est un itérable, alors
freeze()est appelée sur tous les éléments de l’itérable (avec les mêmes path et opt transmis).Si script est une chaîne, alors elle spécifie le fichier ou le répertoire à figer, et peut inclure des répertoires supplémentaires avant le fichier ou le dernier répertoire. Le fichier ou le répertoire sera recherché dans path. Si script est un répertoire, alors tous les fichiers de ce répertoire seront figés.
opt est le niveau d’optimisation à transmettre à mpy-cross lors de la compilation de
.pyen.mpy. Ces niveaux sont décrits dansmicropython.opt_level().
- freeze_as_str(path)¶
Fige le path donné et tous les scripts
.pyqu’il contient sous forme de chaîne, qui sera compilée lors de l’importation. N’utilisez ceci que lorsque le code figé doit rester sous forme de sources Python ; cela coûte de la RAM au moment de l’importation par rapport aux variantes.mpy.
- freeze_as_mpy(path, script=None, opt=0)¶
Fige l’entrée en compilant d’abord les scripts
.pyen fichiers.mpy, puis en figeant les fichiers.mpyrésultants. C’est ce que fontmoduleetpackageen coulisses. Voirfreeze()pour plus de détails sur les arguments.
- freeze_mpy(path, script=None, opt=0)¶
Fige l’entrée, qui doit être des fichiers
.mpyfigés directement (sans étape de compilation). Voirfreeze()pour plus de détails sur les arguments.
Exemples¶
Pour figer un fichier unique du répertoire courant qui sera disponible via import mydriver, utilisez :
module("mydriver.py")
Pour figer un répertoire de fichiers dans un sous-répertoire « mydriver » du répertoire courant qui sera disponible via import mydriver, utilisez :
package("mydriver")
Pour figer la bibliothèque « hmac » de micropython-lib, utilisez :
require("hmac")
Un exemple plus complet d’un fichier manifest.py personnalisé (pour une carte ayant son propre manifest par défaut) est :
# 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")
La carte peut alors être compilée avec
$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py
Notez que la plupart des cartes n’ont pas leur propre manifest.py ; elles utilisent plutôt directement celui du port, auquel cas votre manifest devrait simplement faire include("$(PORT_DIR)/boards/manifest.py") à la place.