.. _manifest:

MicroPython manifest files
==========================

Summary
-------

MicroPython has a feature that allows Python code to be "frozen" into the
firmware, as an alternative to loading code from the filesystem.

This has the following benefits:

- the code is pre-compiled to bytecode, avoiding the need for the Python
  source to be compiled at load-time.
- the bytecode can be executed directly from ROM (i.e. flash memory) rather than
  being copied into RAM. Similarly any constant objects (strings, tuples, etc)
  are loaded from ROM also. This can lead to significantly more memory being
  available for your application.
- on devices that do not have a filesystem, this is the only way to
  load Python code.

During development, freezing is generally not recommended as it will
significantly slow down your development cycle, as each update will require
re-flashing the entire firmware. However, it can still be useful to
selectively freeze some rarely-changing dependencies (such as third-party
libraries).

The way to list the Python files to be frozen into the firmware is via
a "manifest", which is a Python file that will be interpreted by the build
process. Typically you would write a manifest file as part of a board
definition, but you can also write a stand-alone manifest file and use it with
an existing board definition.

Manifest files can define dependencies on libraries from :term:`micropython-lib`
as well as Python files on the filesystem, and also on other manifest files.

Writing manifest files
----------------------

A manifest file is a Python file containing a series of function calls. See the
available functions defined below.

Any paths used in manifest files can include the following variables. These all
resolve to absolute paths.

- ``$(MPY_DIR)`` -- path to the micropython repo.
- ``$(MPY_LIB_DIR)`` -- path to the micropython-lib submodule. Prefer to use
  ``require()``.
- ``$(PORT_DIR)`` -- path to the current port (e.g. ``ports/stm32``)
- ``$(BOARD_DIR)`` -- path to the current board
  (e.g. ``ports/stm32/boards/PYBV11``)

Custom manifest files should not live in the main MicroPython repository. You
should keep them in version control with the rest of your project.

Typically a manifest used for compiling firmware will need to include the port
manifest, which might include frozen modules that are required for the board to
function. If you just want to add additional modules to an existing board, then
include the board manifest (which will in turn include the port manifest).

Building with a custom manifest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Your manifest can be specified on the ``make`` command line with:

.. code-block:: bash

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

This applies to all ports, including CMake-based ones (e.g. esp32, rp2), as the
Makefile wrapper that will pass this into the CMake build.

Adding a manifest to a board definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have a custom board definition, you can make it include your custom
manifest automatically. On make-based ports (most ports), in your
``mpconfigboard.mk`` set the ``FROZEN_MANIFEST`` variable.

.. code-block:: makefile

    FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

On CMake-based ports (e.g. esp32, rp2), instead use ``mpconfigboard.cmake``

.. code-block:: cmake

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

High-level functions
~~~~~~~~~~~~~~~~~~~~

Note: The ``opt`` keyword argument can be set on the various functions, this controls
the optimisation level used by the cross-compiler.
See :func:`micropython.opt_level`.

.. function:: add_library(library, library_path, prepend=False)

    Register the path to an external named *library*.

    The path *library_path* will be automatically searched when using `require`.
    By default the added library is added to the end of the list of libraries to
    search.  Pass ``True`` to *prepend* to add it to the start of the list.

    Additionally, the added library can be explicitly requested by using
    ``require("name", library="library")``.

.. function:: package(package_path, files=None, base_path=".", opt=None)

    This is equivalent to copying the "package_path" directory to the device
    (except as frozen code).

    In the simplest case, to freeze a package "foo" in the current directory:

    .. code-block:: python3

        package("foo")

    will recursively include all .py files in foo, and will be frozen as
    ``foo/**/*.py``.

    If the package isn't in the same directory as the manifest file, use ``base_path``:

    .. code-block:: python3

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

    You can use the variables above, such as ``$(PORT_DIR)`` in ``base_path``.

    To restrict to certain files in the package use ``files`` (note: paths
    should be relative to the package): ``package("foo", files=["bar/baz.py"])``.

.. function:: module(module_path, base_path=".", opt=None)

    Include a single Python file as a module.

    If the file is in the current directory:

    .. code-block:: python3

        module("foo.py")

    Otherwise use base_path to locate the file:

    .. code-block:: python3

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

    You can use the variables above, such as ``$(PORT_DIR)`` in ``base_path``.

.. function:: require(name, library=None)

    Require a package by name (and its dependencies) from :term:`micropython-lib`.

    Optionally specify *library* (a string) to reference a package from a
    library that has been previously registered with `add_library`. Otherwise
    the list of library paths will be used.

.. function:: include(manifest_path)

    Include another manifest.

    Typically a manifest used for compiling firmware will need to include the
    port manifest, which might include frozen modules that are required for
    the board to function.

    The *manifest* argument can be a string (filename) or an iterable of
    strings.

    Relative paths are resolved with respect to the current manifest file.

    If the path is to a directory, then it implicitly includes the
    manifest.py file inside that directory.

    You can use the variables above, such as ``$(PORT_DIR)`` in ``manifest_path``.

.. function:: metadata(description=None, version=None, license=None, author=None)

    Define metadata for this manifest file. This is useful for manifests for
    micropython-lib packages.

Low-level functions
~~~~~~~~~~~~~~~~~~~

These functions are documented for completeness, but with the exception of
``freeze_as_str`` all functionality can be accessed via the high-level functions.

.. function:: freeze(path, script=None, opt=0)

    Freeze the input specified by *path*, automatically determining its type.  A
    ``.py`` script will be compiled to a ``.mpy`` first then frozen, and a
    ``.mpy`` file will be frozen directly.

    *path* must be a directory, which is the base directory to begin searching
    for files.  When importing the resulting frozen modules, the name of the
    module will start after *path*, i.e. *path* is excluded from the module
    name.

    If *path* is relative, it is resolved to the current ``manifest.py``.

    If *script* is None, all files in *path* will be frozen.

    If *script* is an iterable then ``freeze()`` is called on all items of the
    iterable (with the same *path* and *opt* passed through).

    If *script* is a string then it specifies the file or directory to freeze,
    and can include extra directories before the file or last directory.  The
    file or directory will be searched for in *path*.  If *script* is a
    directory then all files in that directory will be frozen.

    *opt* is the optimisation level to pass to mpy-cross when compiling ``.py``
    to ``.mpy``.  These levels are described in :func:`micropython.opt_level`.

.. function:: freeze_as_str(path)

    Freeze the given *path* and all ``.py`` scripts within it as a string, which
    will be compiled upon import.

.. function:: freeze_as_mpy(path, script=None, opt=0)

    Freeze the input by first compiling the ``.py`` scripts to ``.mpy`` files,
    then freezing the resulting ``.mpy`` files.  See ``freeze()`` for further
    details on the arguments.

.. function::   freeze_mpy(path, script=None, opt=0)

    Freeze the input, which must be ``.mpy`` files that are frozen directly.
    See ``freeze()`` for further details on the arguments.

Examples
--------

To freeze a single file from the current directory which will be available as
``import mydriver``, use:

.. code-block:: python3

    module("mydriver.py")

To freeze a directory of files in a subdirectory "mydriver" of the current
directory which will be available as ``import mydriver``, use:

.. code-block:: python3

    package("mydriver")

To freeze the "hmac" library from :term:`micropython-lib`, use:

.. code-block:: python3

    require("hmac")

A more complete example of a custom ``manifest.py`` file for the ``PYBD_SF2``
board is:

.. code-block:: python3

    # 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")

Then the board can be compiled with

.. code-block:: bash

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

Note that most boards do not have their own ``manifest.py``, rather they use the
port one directly, in which case your manifest should just
``include("$(PORT_DIR)/boards/manifest.py")`` instead.
