分发包、包管理和部署应用程序

正如“大”Python 一样,MicroPython 支持创建“第三方”包,分发它们,并在每个用户的环境中轻松安装它们。本章讨论如何实现这些操作。建议对 Python 打包有一定的了解。

概述

以下步骤表示创建和使用包时的高级工作流程:

  1. Python 模块和包被转换为分发包档案,并在 Python 包索引 (PyPI) 上发布。

  2. upip包管理器可用于在具有网络功能的MicroPython 端口 上安装分发包(例如,在 Unix 端口上)。

  3. 对于没有联网能力的端口,可以在Unix端口上准备一个“安装映像”,并通过合适的方式传输到设备上。

  4. 对于低内存端口,可以将安装映像冻结为 MicroPython 可执行文件的字节码,从而最大限度地减少内存存储开销。

以下部分详细描述了此过程。

分发包

Python 模块和包可以打包成适合系统间传输的归档文件,存储在众所周知的位置(PyPI),并按需下载部署。这些档案被称为 分发包(以区别于 Python 包(意味着组织 Python 源代码))。

MicroPython 分发包格式是众所周知的 tar.gz 格式,但有一些改编。Gzip 压缩器用作 TAR 档案的外部包装器,默认情况下使用 32KB 字典大小,这意味着要解压缩压缩流,需要分配 32KB 的连续内存。在低内存设备上可能无法满足此要求,这些设备的可用内存总量可能少于该数量,即使没有,由于内存碎片,像这样的连续块也可能难以分配。为了适应这些限制,MicroPython 分发包使用字典大小为 4K 的 Gzip 压缩,这应该是一个合适的折衷方案,即使在最小的设备上也能解压缩,同时仍能实现一些压缩。

除了较小的压缩字典大小外,MicroPython 分发包还有其他优化,例如从存档中删除安装过程中未使用的任何文件。特别是, upip 包管理器setup.py 在安装期间不执行(见下文),因此该文件不包含在存档中。

同时,这些优化使得 MicroPython 分发包与CPython的包管理器pip. 这不被认为是一个大问题,因为:

  1. 包可以使用 upip安装,然后可以与 CPython 一起使用(如果它们兼容的话)。

  2. 另一方面,由于各种原因,大多数 CPython 包与 MicroPython 不兼容,首先,对 MicroPython 未实现的功能的依赖。

总之,MicroPython 分发包存档针对 MicroPython 的目标环境进行了高度优化,这些环境是资源高度受限的设备。

upip包管理器

MicroPython 分发包旨在使用upip 包管理器进行安装。upip是一个 Python 应用程序,它通常通过启用网络的 MicroPython 端口分发(作为冻结的字节码) 。至少, upipMicroPython Unix port中可用。

在任何提供upipMicroPython 端口上可以按如下方式访问它:

import upip
upip.help()
upip.install(package_or_package_list, [path])

其中package_or_package_list是要安装的分发包的名称,或者是用于安装多个包的此类名称的列表。可选路径参数指定要安装的文件系统位置,默认为标准库位置(见下文)。

安装特定软件包然后使用它的示例:

>>> import upip
>>> upip.install("micropython-pystone_lowmem")
[...]
>>> import pystone_lowmem
>>> pystone_lowmem.main()

请注意,Python 包的名称和它的分发包的名称通常不必匹配,并且通常它们不匹配。这是因为 PyPI 为所有不同的 Python 实现和版本提供了一个中央包存储库,因此分发包名称可能需要为特定实现命名。例如,所有包都micropython-lib 遵循此命名约定:对于名为 的 Python 模块或包 foo分发包名称为micropython-foo.

对于从操作系统命令提示符(如 Unix 端口)运行 MicroPython 可执行文件的端口,upip 可以(实际上,通常是)从命令行而不是 MicroPython 自己的 REPL 运行。与上述示例对应的命令是:

micropython -m upip -h
micropython -m upip install [-p <path>] <packages>...
micropython -m upip install micropython-pystone_lowmem

[TODO:描述安装路径。]

交叉安装包

对于没有本地网络功能的MicroPython 端口,推荐的过程是使用MicroPython Unix 端口将它们“交叉安装”到“目录映像”中,然后通过合适的方式将此映像传输到设备。

安装到目录映像涉及使用-p switch 到 upip:

micropython -m upip install -p install_dir micropython-pystone_lowmem

执行此命令后,install_dir/ 子目录中将提供包内容(以及每个依赖包的内容)。您需要将此目录的内容(不带 install_dir/前缀)传输到设备的合适位置,该位置可以通过 Pythonimport语句找到(请参阅上面对 upip 安装路径的讨论)。

交叉安装包与冻结

对于低内存的 MicroPython 端口,上一节描述的过程并没有提供最有效的资源使用,因为包是以源代码形式安装的,所以需要在每次导入时编译为字节码。此编译需要 RAM,并且生成的字节码也存储在 RAM 中,从而减少了可用于存储应用程序数据的数量。此外,上述过程需要设备上存在文件系统,而大多数资源受限的设备甚至可能没有它。

字节码冻结是一个解决上述所有问题的过程:

  • 源代码被预编译成字节码并按原样存储。

  • 字节码存储在 ROM 中,而不是 RAM 中。

  • 冻结包不需要文件系统。

使用冻结字节码需要从 C 源代码为给定的MicroPython 端口构建可执行文件(固件)。因此,该过程是:

  1. 按照特定端口的说明设置工具链和构建端口。例如,对于 ESP8266 端口,请学习ports/esp8266/README.md 并遵循其中的说明。在继续下一步之前,请确保您可以构建端口并成功部署生成的可执行文件/固件。

  2. 构建MicroPython Unix 端口并确保它在您的 PATH 中并且您可以执行micropython.

  3. 切换到端口目录(例如ports/esp8266/ESP8266)。

  4. 运行。此步骤会清除为冻结而安装的所有先前模块(因此,您需要跳过此步骤以添加其他模块,而不是从头开始)。 make clean-frozen

  5. 运行以安装要冻结的软件包。 micropython -m upip install -p modules <packages>...

  6. 运行。 make clean

  7. 运行 make

在此之后,您应该拥有带有模块的可执行文件/固件作为内部的字节码,您可以按照通常的方式进行部署。

几点注意事项:

  1. 上述序列中的第 5 步假设分发包可从 PyPI 获得。如果不是这种情况,您需要手动将 Python 源文件复制到modules/ port 目录的子目录中。(请注意,upip 不支持从例如版本控制存储库安装)。

  2. 裸机设备的固件通常有大小限制,因此添加过多的冻结模块可能会导致其溢出。通常,如果发生这种情况,您会收到链接错误。但是,在某些情况下,可能会生成无法在设备上运行的映像。此类情况通常是错误,应报告并进一步调查。如果您遇到这种情况,作为初始步骤,您可能希望减少包含的冻结模块的数量。

创建分发包

MicroPython 的分发包以与 CPython 或任何其他 Python 实现相同的方式创建,请参阅章节末尾的参考资料。应该使用 Setuptools(而不是 distutils),因为 distutils 不支持依赖项和其他功能。“源代码分发”( sdist) 格式用于打包。上面讨论的后处理(以及下一节中讨论的预处理)是通过使用sdist setuptools 的自定义命令来实现的。因此,打包步骤与标准设置工具相同,用户只需要sdist 通过传递适当的参数来 setup()调用来覆盖命令实现:

from setuptools import setup
import sdist_upip

setup(
    ...,
    cmdclass={'sdist': sdist_upip.sdist}
)

上面引用的 sdist_upip.py 模块可以在以下位置找到 micropython-lib: https://github.com/micropython/micropython-lib/blob/master/sdist_upip.py

应用资源

一个完整的应用程序,除了源代码之外,通常还包含数据文件,例如网页模板、游戏图像等。手动安装应用程序时如何处理这些很清楚——您只需将这些数据文件放在文件系统中位置并使用正常的文件访问功能。

从包中部署应用程序时情况有所不同——这是一种更先进、更精简和更灵活的方式,但也需要更先进的方法来访问数据文件。这种方法将数据文件视为“资源”,并将对它们的访问抽象化。

Python 使用其“setuptools”库支持资源访问,使用 pkg_resources 模块。MicroPython 遵循其通常的方法,实现该模块功能的子集,特别是 函数。这个想法是应用程序调用这个函数,传递一个资源标识符,它是指定包(通常是顶级应用程序包)内数据文件的相对路径。它返回一个可用于访问资源内容的流对象。因此,模拟标准功能的接口 。pkg_resources.resource_stream(package, resource) resource_stream() open()

在实现方面,resource_stream()如果分发包安装在文件系统中,则在底层使用文件操作。然而,它也支持在没有底层文件系统的情况下运行,例如,如果包被冻结为字节码。然而,这在打包应用程序时需要一个额外的中间步骤 - 创建“Python 资源模块”。

该模块的想法是将二进制数据转换为 Python 字节对象,并将其放入字典中,按资源名称索引。这种转换是使用 sdist 上一节中描述的覆盖命令自动完成的。

让我们使用以下示例跟踪整个过程。假设您的应用程序具有以下结构:

my_app/
    __main__.py
    utils.py
    data/
        page.html
        image.png

__main__.py 并且 utils.py 应该使用以下调用访问资源:

import pkg_resources

pkg_resources.resource_stream(__name__, "data/page.html")
pkg_resources.resource_stream(__name__, "data/image.png")

您可以像往常一样使用 MicroPython Unix 端口进行开发和调试。当需要制作分发包时,只需使用 sdist_upip.py 模块中覆盖的“sdist”命令,如上一节所述

这将创建一个名为 的 Python 资源模块R.py,该模块基于在 MANIFEST或文件中声明的MANIFEST.in文件(任何非.py 文件都将被视为资源并添加到R.py) - 在继续进行正常的打包步骤之前。

像这样准备,您的应用程序将在部署到文件系统和冻结字节码时都可以工作。

如果你想调试R.py创建,你可以运行:

python3 setup.py sdist --manifest-only

或者,您可以使用 MicroPython 发行版中的 tools/mpy_bin2res.py 脚本,您需要在其中传递所有资源文件的路径:

mpy_bin2res.py data/page.html data/image.png

参考