RFC-0256:为 Lacewing 测试捆绑 Python 应用

RFC-0256:为 Lacewing 测试捆绑 Python 应用
状态已接受
领域
  • 构建
  • 软件交付
  • 测试
  • 工具链
说明

提议使用已编译的自解压 Rust 主机二进制文件捆绑 Python Lacewing 测试应用。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-05-22
审核日期(年-月-日)2024-08-08

摘要

提议将 Lacewing 测试捆绑为已编译的 Rust 主机二进制文件,以便面向 Fuchsia 的 短期测试专用 Python 应用程序捆绑和分发 需求(例如驱动程序一致性、CTF)。

此提案未去除在下文中探讨的替代方案 未来。

设计初衷

最近在 Fuchsia IDK 中增加了 RTC 驱动程序一致性测试, 我们现在拥有了一种工作并承受的机制, 树内 Python Lacewing E2E 测试,用于树外 (OOT) 执行。

可以直接向 SDK 消费者分发 Lacewing 测试的功能 Fuchsia 的 2024 年路线图 (即驱动程序一致性测试)。此外, 要求进行有版本控制的测试打包和分发,例如 平台预期测试和主机工具兼容性测试 好处多多。

在扩大这种测试分布模型的采用范围之前,我们必须确保 可持续,避免潜在的技术债务。具体而言, 目前要求下游 Lacewing 测试消费者自行提供 用于执行测试的 Python 运行时容易出错,此方案解决了 来避免重复性问题

利益相关方

教员

  • abarth@google.com

审核者

  • abarth@google.com
  • chaselatta@google.com
  • hjfreyer@google.com
  • keir@google.com
  • tmandry@google.com
  • tonymd@google.com

已咨询

  • awdavies@google.com
  • crjohns@google.com
  • cpu@google.com
  • jamesr@google.com

社交化

此提案通过 Google 文档与 FEC 成员 Pigweed、工具链、SDK 体验和工具团队。该提案 在 FEC 会议上讨论的,支持正式批准 RFC。

目标

  • 消除了 Lacewing 测试二进制文件时的 Python 运行时兼容性问题 开箱即用
  • 将 Lacewing Python 测试打包为单个封闭可执行文件 <ph type="x-smartling-placeholder">
      </ph>
    • 支持 C 扩展库(例如 Fuchsia 控制器
    • 支持数据依赖关系(例如 FIDL IR 文件 - 目前需要 Fuchsia 控制器)
  • 支持在定义 Lacewing 测试的位置捆绑捆绑
  • 支持 Linux 主机环境

非目标

  • 支持 Windows 主机环境
  • 支持 Mac 主机环境

提案

我们提议将基于 Rust 的自定义方法用于 Fuchsia 的即时 Python 因为这种方法既能满足客户的需求 可行,并且与上述所有目标保持一致。

概览

运行 Lacewing 测试所需的所有 Python 组件都将嵌入到 一个已编译的 Rust 二进制文件作为其数据资源(通过 Rust 的 include_bytes! 宏)。运行时,Rust 二进制文件会提取其 Python 内容 创建一个临时目录,构造一个命令,然后执行 Python 应用。

与领先的替代方案 PyInstaller 相比,基于 Rust 的自定义方法 PyInstaller 是最终的首选,因为它可以灵活用于 Fuchsia.git(有关详情,请参阅 PyInstaller 的 拒绝理由部分)。通过 下面总结了这两种方法之间的主要利弊:

  • 基于 Rust 的自定义方法的优势:

    • 可在 Fuchsia.git 中使用
    • implemented且 可同时在本地和基础架构环境中成功运行
    • 采用 Fuchsia 现有 Rust 主机工具链的封闭性保证
    • 不需要额外维护动态链接 Python 运行时
    • 不需要更新 Fuchsia 控制器即可定位嵌入式 FIDL-IR 文件
  • PyInstaller 的优点:

    • 为所有 Python 应用内置通用支持
    • 更好地支持非 Linux 主机平台(例如 Windows)

实现

Lacewing Rust 二进制文件包含 3 个逻辑部分:Lacewing 工件 嵌入、工件提取和测试执行。

嵌入

运行 Lacewing 测试所需的所有组件都将嵌入 作为单个归档文件导入 Rust 二进制文件

目录

嵌入的归档包含以下内容:

  • 静态链接的 Python 运行时及其标准库(Python 模块)
  • PYZ 格式的花边测试(与zipapp Fuchsia.git)
  • Fuchsia 控制器共享库
  • FIDL IR 文件(Fucsia 控制器目前需要)

“Compression”(压缩)

为简单起见,将上面列出的所有内容压缩为单个 归档。当前的实现使用 ZIP 格式,该格式可生成剥离 linux-x64 主机二进制文件,文件大小约为 40MB。未来的迭代可能会选择 压缩比更高的压缩格式,例如 zstd (在主机驱动型环境中, )。

构建

为了捆绑任意 Lacewing 测试,我们会将 GN 构建自动化功能添加到 动态提供用于嵌入的 Lacewing 工件。

我们通过结合使用 rustc_embed_files()rustc_binary() GN 模板,其中前者提供测试专用数据资源作为 Rust 库,而后者包含用于测试的 main() 逻辑 提取和执行

提取和执行

运行时,Rust 二进制文件首先会将嵌入的资源 目录。然后,它会构建用于运行 Lacewing 测试的命令, 引用提取的内容。最后,系统会执行该命令 Rust 二进制文件退出。为了确保封闭,我们还要设置 PYTHONPATH 环境变量,以确保静默 Python 安装 和库。

性能

制作时间 - 草坪测试捆绑包只会对一小部分 Fuchsia 的 Lacewing 测试组合,可有效降低 Rust 二进制文件的构建开销 可以忽略不计。

运行时间 - 由于 Python 应用的端到端测试性质, 它们对因启动映像而产生的微小启动开销没有太大的影响 在提取阶段运行。实证基础架构数据显示,未经优化需要不到 6 秒 考虑到这些测试的平均运行时间大约为 1 分钟,因此可以忽略不计。

大小 - 输出的可执行文件大小约为 40MB,类似于 其他替代工具(如 PyInstaller)。不过,在以下方面仍有改进空间: 嵌入期间使用的压缩算法的选择。

以下是优化各个维度效果的优化建议 随着捆绑的 Lacewing 测试数量的增加,您可以探索这些实验:

  • 构建时间:构建“stem”Rust ELF 二进制文件,其中包含 与测试无关的工件(例如运行时、stdlibs、C 扩展程序)和 将其与测试专用 Zip 存档(例如 test.pyz)串联起来。“stem”ELF 然后,可以通过 ZipArchive 自解压来访问其 ZIP 内容,就像 当前提议的方法;只不过只有 Rust 编译过程 而不是针对每个捆绑的 Lacewing 测试记录一次

  • 运行时 - 使用 GN config 优化 Rust 编译速度。通过 可以测量提取时间,并将其导出到性能跟踪后端 以防止出现回归问题。

  • 大小:与上面的构建时优化类似,通过拆分 Rust ELF 进行 二进制文件转换为不限定测试的“stem”和测试专用的 Zip 归档,我们 分布大的“主干”与那些 测试专用 Zip 存档。这让我们可以节省存储空间和网络带宽 冗余地包含“stem”在每个捆绑的 Lacewing 测试中测试更多信息请参见 可执行文件大小

工效学设计

此方案改善了执行 OOT Lacewing 测试的用户体验 - 集成商现已加入 只需启动一个测试可执行文件。

向后兼容性

添加封闭模式(Rust 捆绑)不需要对 “Lacewing”测试来源也就是说,你可以在 Google Cloud 中 封闭 (./test) 和非封闭 (./python test.pyz) 模式, 对 Python 源代码所做的任何更改。

测试

1) Rust 绑定过程和 2) 所得稳定性 封闭测试将在 Fuchsia 基础架构中进行soak-tested。我们将 与非封闭的对应项进行基准测试;如果没有明显的 不稳定/失败率存在差异,我们将考虑基于 Rust 的 准备好用于生产环境的捆绑方法(例如 CQ 和 SDK 分发)。

风险和资源

可执行文件大小

假设每个捆绑的 Lacewing 二进制文件占用约 40MB 的存储空间, 会造成一些担忧,因为我们将分布式测试的数量 估算值(例如,100 次测试为 4GB)。为了缓解这种风险,可以尝试以下方法 同时又能以次线性的方式扩缩存储和带宽费用: 捆绑了 Lacewing 测试。

  1. 在单体式 Rust 应用中分发所有测试。

    • Lacewing 归档中最大的组件(Python 运行时、标准 libs 和 Fuchsia Controller 扩展程序)分发一次。
    • 随着 Google Cloud 产品数量的增加, Python 应用。
  2. 单独分发测试。

    • 与上文类似,Lacewing 档案中最大的部分是 在单个 Rust 应用中分发一次。
    • 测试 PYZ (500kB) 会单独分发,并提供给主应用 作为运行时参数。
  3. 缩减 Fuchsia 控制器的大小。

    • 由于 Fuchsia 控制器的迁移,该数字将会自然减少 实现静态 Python 绑定(将上游运行时逻辑发送到 fidlgen) <ph type="x-smartling-placeholder">
        </ph>
      • 将移除 fidl_codec.so
      • 静态 Python FIDL 绑定将更简洁,因此体积更小 相比 FIDL IR 文件
  4. 了解上述其他压缩格式和参数 上方

跨平台可行性

将我们的 Python 捆绑解决方案与 Fuchsia 的 Rust 主机工具链相结合意味着 我们将免费获得 Linux x64 支持,但也仅限于 当前支持的平台集目前,Linux x64 已足够 以满足我们的迫切需求。但是,如果附加的主机平台 支持(例如 Windows)成为一项要求,我们需要与 Fuchsia Rust 和 Fuchsia Build 团队负责确定可行性范围。

在撰写本文时,还没有已知的非 Linux-x64 主机平台 “OOT Lacewing”测试的要求。

有限支持

当前提案的范围仅限于支持 Lacewing Python 应用。 但如果需要,可以合理地将此方法扩展到 通用 Python 应用所需的工作量, 其他替代工具(例如 PyInstaller/PyOxidizer)。

如果存在大量项目,则可以考虑替代方案 要求变更,例如:

  • Windows 支持
  • 逐位可重现性
  • 不要在后台启动 Python

安全注意事项

在 Fuchsia 的 SDK 中分发 Rust 主机二进制文件是一个成熟的流程(例如, ffx),因此应用该出价策略不会带来额外的安全风险。 与 Lacewing 测试相同的方法。

隐私注意事项

不适用

文档

不适用

替代方案

PyInstaller

我们特意添加了这个有关 PyInstaller 的大章节,以供日后参考。通过 下面的发现可帮助我们立即提出建议:选择基于 Rust 的自定义 方法,并为将来的努力提供参考依据。

PyInstaller 遭拒的理由

由于 PyInstaller 的 许可,则只有 作为预构建工件在 Fuchsia.git 中,位于“开发目标”下部分 Fuchsia 的开源许可政策。不过,PyInstaller 旨在作为源代码运行,且无法轻松打包到预构建中。在 这一次,我们就没有动力去解决这个重大问题,因为 拥有基于 Rust 的解决方案,该解决方案运行良好、可在树内使用,并能为我们的 直接需求。

概览

PyInstaller 是一个开源软件 第三方 Python 库,用于捆绑 Python 应用及其 将依赖项转换为可在最终用户系统上运行的自包含可执行文件 没有安装 Python。UIt 是 Python 应用打包方式因其易用性和成熟性 (有效版本 自 2015 年以来)。

当 PyInstaller 在“one-file”中运行时它会将它启动的运行时捆绑在一起 与所有应用模块一起传递到输出可执行文件中。时间 输出可执行文件运行时,PyInstaller 的引导加载程序将提取其嵌入式 归档文件(包含 Python 解释器、内置/用户提供的 Python) 模块、内置/用户提供的共享库和数据文件)构建为 然后在封闭上下文中执行解释器。

借助 PyInstaller,我们能够将 Lacewing 测试捆绑到一个封闭式中, 可移植的可执行文件(Python 运行时 + 测试模块 + 库模块 + Fuchsia) 控制器 C 扩展程序 + 数据依赖项,例如 FIDL IR) 各种 Linux 主机环境

遗憾的是,静态链接的 Python 运行时,例如 Fuchsia 的供应商 Python 非 与 PyInstaller 兼容 (相关问题:12)。解决方法是 在 Chromium 的 第三方合作伙伴 基础架构(第三方软件包)。构建完毕后,动态链接版本 (以后为 DL-CPython3)可以在 Fuchsia.git 中固定为预构建版本, 满足 Fuchsia 的 Python 应用打包需求。

由于构建 DL-CPython3 的源代码与静态 链接版本(以下为 ST-CPython3),已在 Fuchsia 中使用, DL-CPython3 无需额外的许可审批。

工作原型

Fuchsia.git CL 演示了 通过 PyInstaller 通过 Fuchsia 的 GN 打包 Lacewing 测试的可行性 构建系统通过原型 CL,我们确认可以 通过在源代码运行 PyInstaller 以测试封闭的 Lacewing Python 可执行文件 GN 时间。

当时展示 CL 在 Fuchsia 基础架构中正常工作是不可行的 因为使用了未提交的依赖项 在原型设计中。尽管我们预计在基础架构支持方面不会遇到任何挑战 一旦这些依赖项在 Fuchsia 源代码中可用。

未提交的依赖项
  • DL-CPython3: Chromium 3PP 但尚未固定在 Fuchsia.git 中。

  • PyInstaller 库:PyInstaller 及其传递 Python 依赖项 OSRB 审核已完成,但只能在 Fuchsia.git 中用作预构建二进制文件。

紫红色控制器

需要更新 Fuchsia 控制器才能找到其数据依赖关系, 运行时。当 PyInstaller 可执行文件运行时,其内容将解压缩, 从临时目录执行这个 补丁 Fuchsia 控制器变为可感知 PyInstaller,并在 作为 PyInstaller 可执行文件运行时,指定临时目录。

Hermeticity

在检查其 build 时,已确认 PyInstaller 的输出是封闭的 元数据 - Analysis.toc。其中包含的所有内容都 源自 $FUCHSIA_DIR - 也就是说,没有氛围系统共享库 资源。

此外,对 PyInstaller 输出运行 ldd 将显示一组共享 通用依赖项库和依赖项库, Linux 发行版。

~/fuchsia$ ldd dist/soft_reboot_test_fc
linux-vdso.so.1
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2

为防止密封性随着时间的推移而受到影响,可以添加构建时检查 以确认 PyInstaller 可执行文件的 ldd 输出存在于许可名单中。

大小

在原型中不考虑大小优化时, 通过 PyInstaller 打包的软重启 Lacewing 测试大小约为 43 MB (作为参考,PYZ 版本约为 400KB)。

目前有许多尚未探索的方法来缩减 PyInstaller 的大小 草坪测试:

  1. 使用 UPX 压缩文件的大小。
  2. 排除未使用的内置扩展模块。

运行时管理和维护

Fuchsia 团队需要同时维护 DL-CPython3(适用于已版本化的 Lacewing) 测试封装)和 ST-CPython3(适用于所有其他内容),并确保 版本功能等效,并且按步同步(例如,共享 CIPD 版本标记相同)。这就需要您与 Google 内部 PEEP 软件部署团队简化 2023 年第 2 季度的 版本经过编程,可防止出现差异。

跨平台可行性

PyInstaller 不支持交叉编译 因此我们需要在相应的目标环境中运行 PyInstaller (例如 Windows、Mac)来生成特定于主机平台的可执行文件。

例如,要支持 Windows 最终用户, 使用 Windows 版本的 DL-CPython3 的 Windows 计算机。在撰写本文时, Fuchsia 没有这种基于 Windows 的构建环境,因此需要限定范围 在我们的构建舰队中添加支持至于适用于 Windows 的 DL-CPython3,请使用 Chromium 3PP 已经支持为多个平台(包括 因此,我们应该能够轻松扩展对平台的支持, Linux(如果需要)。

PyOxidizer

PyOxidizer是 通用 Python 应用打包选项, PyInstaller:

  • 封闭 - PyOxidizer 的 输出是编译的二进制文件,而 PyInstaller 的输出是一个存档, 需要在 Python 代码中解压缩到 Python 位(例如,内置 Python 扩展), 运行时。
  • 速度 - PyOxidizer 的输出 不需要解压即可运行,这使其启动时间比 PyInstaller 的输出。
  • 安全性 - PyOxidizer Rust 语言的安全保证比 PyInstaller 的 Python 和 C。

尽管如此,在端到端测试中,这些优势几乎可以忽略不计 上下文。

PyOxidizer 拒绝理由

最终,我不会选择 PyOxidizer,因为它不满足 将 C 扩展库捆绑在一个可执行文件中的目标。短暂 使用 PyOxidizer 的探索未能将玩具 C 扩展 输出可执行文件。尽管这些实验可能 考虑到 PyOxidizer 的高度可配置性、资源 由于 PyOxidzer 相对新颖且采用率较低 因此,我们暂时搁置了进一步的探索。

先验技术和参考资料