MicroPython マニフェストファイル

概要

MicroPython には、ファイルシステムからコードを読み込む代わりに、Python コードをファームウェアに「フリーズ」する機能があります。

これには次のような利点があります。

  • コードはあらかじめバイトコードにコンパイルされるため、読み込み時に Python ソースをコンパイルする必要がなくなります。

  • バイトコードは RAM にコピーされるのではなく、ROM(つまりフラッシュメモリ)から直接実行できます。同様に、定数オブジェクト(文字列、タプルなど)も ROM から読み込まれます。これにより、アプリケーションで使用できるメモリが大幅に増える可能性があります。

  • ファイルシステムを持たないデバイスでは、これが 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 リポジトリに置くべきではありません。プロジェクトの他の部分とともにバージョン管理下で保持してください。

通常、ファームウェアのコンパイルに使用するマニフェストにはポートマニフェストを含める必要があります。これにはボードが機能するために必要なフリーズ済みモジュールが含まれている場合があります。既存のボードにモジュールを追加するだけであれば、ボードマニフェスト(これがさらにポートマニフェストを含みます)を include してください。

カスタムマニフェストを使用したビルド

マニフェストは make コマンドラインで次のように指定できます。

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

これは CMake ベースのもの(例: rp2)を含むすべてのポートに適用されます。Makefile ラッパーがこれを CMake ビルドに渡すためです。

ボード定義へのマニフェストの追加

カスタムボード定義がある場合は、カスタムマニフェストを自動的に含めるようにできます。make ベースのポート(ほとんどのポート)では、mpconfigboard.mkFROZEN_MANIFEST 変数を設定します。

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

CMake ベースのポート(例: rp2)では、代わりに mpconfigboard.cmake を使用します。

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

高レベル関数

これらは通常使用する関数です。バイトコードにプリコンパイルされてファームウェアイメージにフリーズされる集合にコードを追加します。

  • modulepackage自分自身のローカルソース(それぞれ単一ファイルまたはパッケージディレクトリ全体)をフリーズします。

  • requiremicropython-lib から公開されているパッケージ(とその依存関係)を名前でフリーズします。

  • include は別のマニフェストを取り込み、そのフリーズ済みモジュールも追加されるようにします。

  • add_librarymetadata はサポート関数です(require 用の追加検索パスの登録、およびパッケージメタデータの宣言を行います)。

典型的なファームウェアマニフェストは、まずポートまたはボードのマニフェストを include し(ボードが必要とするモジュールがフリーズされたままになるように)、その後に独自の module/package/require 行を追加します。

注: さまざまな関数で opt キーワード引数を設定でき、これによりクロスコンパイラが使用する最適化レベルを制御します。micropython.opt_level() を参照してください。

add_library(library, library_path, prepend=False)

外部の名前付きライブラリへのパスを登録します。

requiremicropython-lib 以外のディレクトリ(例えば独自のドライバ集やサードパーティ製ライブラリのチェックアウト)からパッケージを解決させたい場合に使用します。

パス library_pathrequire を使用する際に自動的に検索されます。デフォルトでは、追加されたライブラリは検索するライブラリのリストの末尾に追加されます。True を渡すとリストの先頭に前置追加されます。

さらに、追加されたライブラリは require("name", library="library") を使用して明示的に要求することもできます。

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

パッケージ全体、つまり .py ファイルのディレクトリ(オプションでサブパッケージを含む)をフリーズし、import <package> としてインポートできるようにします。単一のスタンドアロンファイルには代わりに module を使用してください。

これは「package_path」ディレクトリをデバイスにコピーするのと同等です(ただしフリーズ済みコードとして)。

最も単純なケースとして、カレントディレクトリのパッケージ「foo」をフリーズするには次のようにします。

package("foo")

foo 内のすべての .py ファイルを再帰的に含め、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 を使用してください。

オプションで、以前に add_library で登録されたライブラリのパッケージを参照するために library(文字列)を指定できます。指定しない場合は、ライブラリパスのリストが使用されます。

include(manifest_path)

別のマニフェストを include します。これがマニフェストを構成する方法です。カスタムファームウェアマニフェストは、ボードが必要とするモジュールがフリーズされたままになるようにポート(またはボード)マニフェストを include し、その後に独自のエントリを追加すべきです。

通常、ファームウェアのコンパイルに使用するマニフェストにはポートマニフェストを含める必要があります。これにはボードが機能するために必要なフリーズ済みモジュールが含まれている場合があります。

manifest 引数には、文字列(ファイル名)または文字列のイテラブルを指定できます。

相対パスは現在のマニフェストファイルを基準に解決されます。

パスがディレクトリを指す場合は、そのディレクトリ内の manifest.py ファイルが暗黙的に含まれます。

manifest_path では、上記のような $(PORT_DIR) などの変数を使用できます。

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

このマニフェストファイルのメタデータを定義します。これは micropython-lib パッケージのマニフェストで役立ちます。

これらのフィールドは、パッケージが mip 経由で micropython-lib に公開される/からインストールされる際に使用されます。ボードファームウェアマニフェストでは不要です。

低レベル関数

これらの関数は完全性のために文書化されていますが、freeze_as_str を除いて、すべての機能は高レベル関数を介してアクセスできます。

freeze* 関数は、コードの保存方法のみが異なります。

  • freeze_as_mpy / freeze_mpy は、プリコンパイルされたバイトコード.mpy)をフラッシュに保存します。コードはフラッシュから直接実行され、RAM の使用を最小限に抑え、すばやくインポートされます。これは modulepackagerequire が内部的に使用するものです。

  • freeze_as_str は代わりに Python のソースをフリーズし、インポート時にバイトコードにコンパイルされます(RAM を使用し、デバイス上のコンパイラを必要とします)。これは高レベル関数では公開されていない唯一の機能であり、これが上記で例外として記載されている理由です。

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

高レベル関数が構築の基盤とする基本プリミティブですが、それらの利用を推奨します。path で指定された入力をフリーズし、その種類を自動的に判定します。.py スクリプトはまず .mpy にコンパイルされてからフリーズされ、.mpy ファイルは直接フリーズされます。

path はディレクトリでなければなりません。これはファイルの検索を開始するベースディレクトリです。生成されたフリーズ済みモジュールをインポートする際、モジュール名は path の後から始まります。つまり、path はモジュール名から除外されます。

path が相対パスの場合、現在の manifest.py を基準に解決されます。

script が None の場合、path 内のすべてのファイルがフリーズされます。

script がイテラブルの場合、そのイテラブルのすべての項目に対して freeze() が呼び出されます(同じ pathopt が渡されます)。

script が文字列の場合、フリーズするファイルまたはディレクトリを指定し、ファイルや最後のディレクトリの前に追加のディレクトリを含めることができます。ファイルまたはディレクトリは path 内で検索されます。script がディレクトリの場合、そのディレクトリ内のすべてのファイルがフリーズされます。

opt.py.mpy にコンパイルする際に mpy-cross に渡す最適化レベルです。これらのレベルは micropython.opt_level() で説明されています。

freeze_as_str(path)

指定された path とその中のすべての .py スクリプトを文字列としてフリーズします。これはインポート時にコンパイルされます。フリーズされたコードを Python ソースのまま保持する必要がある場合にのみ使用してください。.mpy 版と比べてインポート時の RAM を消費します。

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

まず .py スクリプトを .mpy ファイルにコンパイルし、その後生成された .mpy ファイルをフリーズすることで入力をフリーズします。これは modulepackage が内部で行っていることです。引数の詳細については freeze() を参照してください。

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

入力をフリーズします。入力は直接フリーズされる .mpy ファイルでなければなりません(コンパイル手順はありません)。引数の詳細については freeze() を参照してください。

カレントディレクトリから import mydriver として利用できる単一ファイルをフリーズするには、次のようにします。

module("mydriver.py")

カレントディレクトリのサブディレクトリ「mydriver」内のファイルのディレクトリを import mydriver として利用できるようにフリーズするには、次のようにします。

package("mydriver")

micropython-lib から「hmac」ライブラリをフリーズするには、次のようにします。

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") とするべきです。