MicroPython 매니페스트 파일

요약

MicroPython에는 파일 시스템에서 코드를 로드하는 방식의 대안으로, Python 코드를 펌웨어에 “고정(freeze)”할 수 있는 기능이 있습니다.

이 방식에는 다음과 같은 이점이 있습니다.

  • 코드가 미리 바이트코드로 컴파일되므로, 로드 시점에 Python 소스를 컴파일할 필요가 없습니다.

  • 바이트코드를 RAM으로 복사하지 않고 ROM(즉, 플래시 메모리)에서 직접 실행할 수 있습니다. 마찬가지로 상수 객체(문자열, 튜플 등)도 ROM에서 로드됩니다. 이를 통해 애플리케이션에 사용할 수 있는 메모리가 상당히 늘어날 수 있습니다.

  • 파일 시스템이 없는 장치에서는 Python 코드를 로드하는 유일한 방법입니다.

개발 중에는 일반적으로 고정(freeze)을 권장하지 않습니다. 업데이트할 때마다 전체 펌웨어를 다시 플래시해야 하므로 개발 주기가 크게 느려지기 때문입니다. 그러나 거의 변경되지 않는 일부 의존성(예: 서드파티 라이브러리)을 선택적으로 고정하는 것은 여전히 유용할 수 있습니다.

펌웨어에 고정할 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

이는 Makefile 래퍼가 이를 CMake 빌드에 전달하므로 CMake 기반 포트(예: rp2)를 포함한 모든 포트에 적용됩니다.

보드 정의에 매니페스트 추가하기

사용자 정의 보드 정의가 있는 경우, 사용자 정의 매니페스트가 자동으로 포함되도록 만들 수 있습니다. 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)

외부 이름이 지정된 library의 경로를 등록합니다.

requiremicropython-lib가 아닌 다른 디렉터리에서 패키지를 해석하도록 하려는 경우 이를 사용하십시오. 예를 들어 자체 드라이버 모음이나 서드파티 라이브러리 체크아웃 등이 있습니다.

library_path 경로는 require 사용 시 자동으로 검색됩니다. 기본적으로 추가된 라이브러리는 검색할 라이브러리 목록의 끝에 추가됩니다. 목록의 시작 부분에 추가하려면(prepend) 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하여 보드에 필요한 모듈이 고정 상태로 유지되도록 한 다음, 자체 항목을 추가해야 합니다.

일반적으로 펌웨어 컴파일에 사용되는 매니페스트는 포트 매니페스트를 포함해야 하며, 여기에는 보드가 작동하는 데 필요한 고정 모듈이 포함될 수 있습니다.

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는 사전 컴파일된 bytecode(.mpy)를 플래시에 저장합니다. 코드는 플래시에서 직접 실행되며, 최소한의 RAM을 사용하고 빠르게 import됩니다. 이것이 module, package, require가 내부적으로 사용하는 방식입니다.

  • 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")만 사용하면 됩니다.