MicroPython .mpy fájlok

A MicroPython bevezeti az .mpy fájl fogalmát, amely egy bináris konténer-fájlformátum, ami előfordított kódot tárol, és úgy importálható, mint egy normál .py modul. A foo.mpy fájl az import foo paranccsal importálható, amennyiben a foo.mpy a szokásos módon megtalálható az importáló mechanizmus számára. Általában a sys.path listában felsorolt könyvtárak mindegyikében sorban történik a keresés. Egy adott könyvtárban először a foo.py fájlt keresi a rendszer, és ha az nem található, akkor a foo.mpy fájlt, majd ha egyik sincs meg, a keresés a következő könyvtárban folytatódik. Ennek megfelelően a foo.py elsőbbséget élvez a foo.mpy fájllal szemben.

Ezek az .mpy fájlok bytecode kódot tartalmazhatnak, amelyet általában a mpy-cross programmal állítanak elő Python forrásfájlokból (.py fájlokból). Egyes architektúrák esetében az .mpy fájl natív gépi kódot is tartalmazhat, amely többféle módon, leginkább C forráskódból állítható elő.

Az mpy-cross fordító

A mpy-cross az a keresztfordító, amely egy .py forrásfájlt egy .mpy bináris konténerré alakít, amely készen áll a kamerára való importálásra. Ez a MicroPython forrásfa része (ugyanazé, amelyből a kamera firmware-ét építik), és pip csomagként is közzéteszik, hogy a gazdagép oldalán teljes firmware-letöltés nélkül használható legyen:

$ pip install --user mpy-cross

Vagy a pipx segítségével:

$ pipx install mpy-cross

A telepítés után hívd meg egyetlen forrásfájlra:

$ mpy-cross foo.py

Ez létrehozza a foo.mpy fájlt az aktuális könyvtárban, amely készen áll arra, hogy a többi modul mellé átmásold a kamera fájlrendszerébe, vagy egy ROMFS image-be illeszd.

A leghasznosabb parancssori kapcsolók:

  • -o <path> – a generált .mpy kimeneti útvonala (alapértelmezés szerint a bemeneti fájlnév, lecserélt kiterjesztéssel; az -o - a stdout-ra ír).

  • -O<n> – optimalizálási szint 0 és 3 között. Az alapértelmezett 0 megőrzi az asszerciókat és a teljes forráshelyeket; a 3 eltávolítja az asszerciókat és docstringeket, valamint átírja az if __debug__ blokkokat. A szint ugyanazt a micropython.opt_level felületet vezérli, amelyet a futtatókörnyezet is elérhetővé tesz.

  • -march=<arch> – a @native és @viper dekorátorral ellátott függvények cél natív architektúrája. Kötelező, ha a forrás ezeket a dekorátorokat használja. Az értéknek a kamera MCU-osztályához kell illeszkednie: válaszd ki a mpy-cross --help által kiírt listából, vagy olvasd ki a kameráról futásidőben a sys.implementation._mpy segítségével.

  • -s <path> – az .mpy hibakeresési információjába beágyazott forrásútvonal-karakterlánc. Hasznos, ha a lemezen lévő útvonal eltér attól az importálási útvonaltól, amely alatt a fájlnak meg kell jelennie a hibakövetésekben.

  • -X emit=bytecode|native|viper – az egész modul alapértelmezett kibocsátójának kiválasztása (függvényenkénti alternatíva a @native / @viper dekorátorokhoz).

  • --version – kiírja az .mpy formátum azon verzióját, amelyet ez a bináris kibocsát. Ennek a számnak meg kell egyeznie a kamera futtatókörnyezete által támogatott verzióval (lásd a lenti kiadási táblázatot), különben az importálás ValueError('incompatible .mpy file') hibát vált ki.

A teljes kapcsolólistáért futtasd a mpy-cross --help parancsot.

A pip csomag egy kicsi Python modul API-t is elérhetővé tesz, így a build szkriptek a fordítót folyamaton belül vezérelhetik, ahelyett, hogy kézzel egy alfolyamatot forkolnának:

import mpy_cross

mpy_cross.compile('foo.py', dest='build/foo.mpy', opt=3,
                  march=mpy_cross.NATIVE_ARCH_ARMV7EMSP)

A mpy_cross.compile, mpy_cross.run és mpy_cross.mpy_version a három belépési pont; a mpy_cross.CrossCompileError hordozza a fordító stderr kimenetét, ha valami hibázik. Az architektúra-konstansok (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP stb.) megfelelnek a -march kapcsoló által elfogadott karakterláncoknak.

Az .mpy fájlok verziókezelése és kompatibilitása

Egy adott .mpy fájl lehet, hogy kompatibilis egy adott MicroPython rendszerrel, de lehet, hogy nem. A kompatibilitás a következőkön alapul:

  • Az .mpy fájl verziója: a fájl verziójának meg kell egyeznie az azt betöltő rendszer által támogatott verzióval.

  • Az .mpy fájl alverziója: ha az .mpy fájl natív gépi kódot tartalmaz, akkor a fájl alverziójának meg kell egyeznie az azt betöltő rendszer által támogatott verzióval. Egyébként, ha az .mpy fájlban nincs natív gépi kód, akkor az alverziót a betöltéskor figyelmen kívül hagyja a rendszer.

  • Kis egész szám bitjei: az .mpy fájl egy minimális számú bitet igényel egy small integer típushoz, és az azt betöltő rendszernek legalább ennyi bitet kell támogatnia.

  • Natív architektúra: ha az .mpy fájl natív gépi kódot tartalmaz, akkor megadja ennek a gépi kódnak az architektúráját, és az azt betöltő rendszernek támogatnia kell az adott architektúra kódjának végrehajtását.

Ha egy MicroPython rendszer támogatja az .mpy fájlok importálását, akkor a sys.implementation._mpy mező létezik, és egy egész számot ad vissza, amely kódolja a verziót (alsó 8 bit), a jellemzőket és a natív architektúrát.

Egy olyan .mpy fájl importálásának megkísérlése, amely megbukik az első négy teszt valamelyikén, ValueError('incompatible .mpy file') hibát vált ki. Egy olyan .mpy fájl importálásának megkísérlése, amely megbukik a natív architektúra teszten (ha natív gépi kódot tartalmaz), ValueError('incompatible .mpy arch') hibát vált ki.

Ha egy .mpy fájl importálása sikertelen, próbáld meg a következőket:

  • Határozd meg a MicroPython rendszered által támogatott .mpy verziót és jelzőket a következő végrehajtásával:

    import sys
    sys_mpy = sys.implementation._mpy
    arch = [None, 'x86', 'x64',
        'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp',
        'xtensa', 'xtensawin', 'rv32imc', 'rv64imc'][(sys_mpy >> 10) & 0x0F]
    print('mpy version:', sys_mpy & 0xff)
    print('mpy sub-version:', sys_mpy >> 8 & 3)
    print('mpy flags:', end='')
    if arch:
        print(' -march=' + arch, end='')
    if (sys_mpy >> 16) != 0:
        print(' -march-flags=' + (sys_mpy >> 16), end='')
    print()
    
  • Ellenőrizd az .mpy fájl érvényességét a fájl első két bájtjának megvizsgálásával. Az első bájtnak egy nagy «M» betűnek kell lennie, a második bájt pedig a verziószám, amelynek meg kell egyeznie a fenti rendszerverzióval. Ha nem egyezik, építsd újra az .mpy fájlt.

  • Ellenőrizd, hogy a rendszer .mpy verziója megegyezik-e az .mpy fájl építéséhez használt mpy-cross által kibocsátott verzióval, amelyet a mpy-cross --version mutat meg. Ha nem egyezik, fordítsd újra a mpy-cross programot a mpy-cross --version által jelentett tag-en (vagy hash-en) kicsekkolt Git repóból.

  • Győződj meg róla, hogy a megfelelő mpy-cross jelzőket használod, amelyeket a fenti kód mutat meg, vagy az általad használt porthoz tartozó MPY_CROSS_FLAGS Makefile változó megvizsgálásával.

  • Ha az .mpy fájl harmadik bájtjának 6. bitje be van állítva, akkor ellenőrizd, hogy a kódolt architektúra-specifikus jelzőbitek vuint értéke kompatibilis-e azzal a céllal, amelyen a fájlt importálod.

Az alábbi táblázat a MicroPython kiadás és az .mpy verzió közötti megfelelést mutatja.

MicroPython kiadás

.mpy verzió

v1.23.0 és újabb

6.3

v1.22.x

6.2

v1.20 - v1.21.0

6.1

v1.19.x

6

v1.12 - v1.18

5

v1.11

4

v1.9.3 - v1.10

3

v1.9 - v1.9.2

2

v1.5.1 - v1.8.7

0

A teljesség kedvéért a következő táblázat a fő MicroPython repó azon Git commitját mutatja, amelynél az .mpy verzió megváltozott.

.mpy verzióváltozás

Git commit

6.2 -> 6.3

bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b

6.1 -> 6.2

6967ff3c581a66f73e9f3d78975f47528db39980

6 -> 6.1

d94141e1473aebae0d3c63aeaa8397651ad6fa01

5 -> 6

f2040bfc7ee033e48acef9f289790f3b4e6b74e5

4 -> 5

5716c5cf65e9b2cb46c2906f40302401bdd27517

3 -> 4

9a5f92ea72754c01cc03e5efcdfe94021120531e

2 -> 3

ff93fd4f50321c6190e1659b19e64fef3045a484

1 -> 2

dd11af209d226b7d18d5148b239662e30ed60bad

0 -> 1

6a11048af1d01c78bdacddadd1b72dc7ba7c6478

kezdeti 0 verzió

d8c834c95d506db979ec871417de90b7951edc30

Az .mpy fájlok bináris kódolása

A MicroPython .mpy fájlok bináris konténerformátumúak, amelyben a kódobjektumok (bytecode és natív gépi kód) belsőleg egy beágyazott hierarchiában tárolódnak. A külső modul kódja tárolódik először, majd a gyermekei következnek. Minden gyermeknek lehetnek további gyermekei, például egy metódusokkal rendelkező osztály esetén, vagy egy lambdát vagy listaértelmezést definiáló függvény esetén. Hogy a fájlok kicsik maradjanak, miközben a lehetséges értékek széles tartományát biztosítják, sok helyen egy változó kódolású előjel nélküli egész szám (vuint) fogalmát használja. Az UTF-8 kódoláshoz hasonlóan ez a kódolás bájtonként 7 bitet tárol, a 8. bittel (MSB) beállítva, ha egy vagy több bájt következik. Az előjel nélküli egész szám bitjei a vuintben LSB sorrendben tárolódnak.

Egy .mpy fájl legfelső szintje három részből áll:

  • A fejléc.

  • A globális qstr és konstans táblák.

  • A modul külső hatóköréhez tartozó nyers kód. Ez a külső hatókör akkor hajtódik végre, amikor az .mpy fájlt importálják.

Egy .mpy fájl tartalmát a mpy-tool.py segítségével vizsgálhatod meg, például (a fő MicroPython repó gyökeréből futtatva):

$ ./tools/mpy-tool.py -xd myfile.mpy

A fejléc

Az .mpy fejléc a következő:

méret

mező

bájt

0x4d érték (ASCII «M»)

bájt

.mpy fő verziószám

bájt

jellemzőjelzők, natív arch, alverziószám (régebbi verziókban jellemzőjelzők volt)

bájt

bitek száma egy kis egész számban

A harmadik bájt a következőképpen oszlik fel (MSB-vel kezdve):

bit

jelentés

7

fenntartott, 0-nak kell lennie

6

egy architektúra-specifikus jelzőket tartalmazó vuint követi a fejlécet

5..2

natív arch szám

1..0

alverziószám

Architektúra-specifikus jelzők

Ha a fejléc jellemzőjelző-bájtjának 6. bitje be van állítva, akkor egy opcionális architektúra-specifikus információt tartalmazó vuint követi a fejlécet. Ennek az egész számnak a tartalma attól függ, hogy a fájl melyik natív architektúrához készült.

Ezt jelenleg arra használják, hogy tárolja, mely RISC-V processzorkiterjesztésekre van szüksége az MPY fájlnak a helyes működéshez az I, M, C és Zicsr kiterjesztéseken kívül. Az ArmV7 különböző változatait a natív architektúra-számuk azonosítja, de ennek a mechanizmusnak az újrahasznosítása bonyolultabbá tenné a dolgokat az RV32 és RV64 esetében.

Az RV32-t vagy RV64-et célzó MPY fájloknak, amelyeknek nincs szükségük semmilyen különleges processzorkiterjesztésre, nem kell jelzőegész számot biztosítaniuk (a fejlécben a megfelelő bit beállítása mellett). Az RV32 és RV64 MPY fájlok esetében a jelzőérték hiánya azt jelzi, hogy nincs szükség specifikus kiterjesztésekre, és egy bájtot takarít meg a végső kimeneti binárisban.

Lásd még a -march-flags parancssori kapcsolót a mpy-tool.py és a mpy-cross programban egyaránt, valamint az --arch-flags parancssori kapcsolót a mpy_ld.py programban, amellyel ezt az értéket beállíthatod MPY fájlok létrehozásakor.

A globális qstr és konstans táblák

Egy .mpy fájl egyetlen qstr táblát és egyetlen konstansobjektum-táblát tartalmaz. Ezek globálisak az .mpy fájlra nézve, és minden beágyazott nyers kód objektum hivatkozik rájuk. A qstr tábla leképezi a belső qstr számot (amely az .mpy fájlon belüli) annak a futtatókörnyezetnek a feloldott qstr számára, amelybe az .mpy fájlt importálják. Ez összekapcsolja az .mpy fájlt a rendszer többi részével, amelyen belül végrehajtódik. A konstansobjektum-tábla az .mpy fájl által igényelt összes konstansobjektumra mutató hivatkozással van feltöltve.

méret

mező

vuint

qstr-ek száma

vuint

konstansobjektumok száma

qstr adat

kódolt konstansobjektumok

Nyers kód elemek

Egy nyers kód elem kódot tartalmaz, akár bytecode-ot, akár natív gépi kódot. A tartalma a következő:

méret

mező

vuint

típus, méret, és hogy vannak-e al-nyers-kód elemek

kód (bytecode vagy gépi kód)

vuint

al-nyers-kód elemek száma (csak ha nem nulla)

al-nyers-kód elemek

Egy nyers kód elem első vuintje kódolja az ebben az elemben tárolt kód típusát (a két legkevésbé jelentős bit), hogy van-e ennek a nyers kódnak gyermeke (a harmadik legkevésbé jelentős bit), és a következő kód hosszát (a számára lefoglalandó RAM mennyiségét).

A vuint után maga a kód következik. Hacsak a kódtípus nem áthelyezéseket tartalmazó viper kód, ez a kód konstans adat, és nem kell módosítani.

Ha ennek a nyers kódnak vannak gyermekei (amint azt az első vuint egy bitje jelzi), a kód után egy vuint következik, amely az al-nyers-kód elemek számát számlálja.

Végül az al-nyers-kód elemek tárolódnak, rekurzívan.