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 测试。
在单体式 Rust 应用中分发所有测试。
- Lacewing 归档中最大的组件(Python 运行时、标准 libs 和 Fuchsia Controller 扩展程序)分发一次。
- 随着 Google Cloud 产品数量的增加, Python 应用。
单独分发测试。
- 与上文类似,Lacewing 档案中最大的部分是 在单个 Rust 应用中分发一次。
- 测试 PYZ (500kB) 会单独分发,并提供给主应用 作为运行时参数。
缩减 Fuchsia 控制器的大小。
- 由于 Fuchsia 控制器的迁移,该数字将会自然减少
实现静态 Python 绑定(将上游运行时逻辑发送到
fidlgen
) <ph type="x-smartling-placeholder">- </ph>
- 将移除
fidl_codec.so
- 静态 Python FIDL 绑定将更简洁,因此体积更小 相比 FIDL IR 文件
- 将移除
- 由于 Fuchsia 控制器的迁移,该数字将会自然减少
实现静态 Python 绑定(将上游运行时逻辑发送到
了解上述其他压缩格式和参数 上方。
跨平台可行性
将我们的 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 兼容 (相关问题:1、 2)。解决方法是 在 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 的大小 草坪测试:
- 使用 UPX 压缩文件的大小。
- 排除未使用的内置扩展模块。
运行时管理和维护
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 相对新颖且采用率较低 因此,我们暂时搁置了进一步的探索。