本页介绍了 hermeticity、使用 hermetic build 的优势,以及用于识别 build 中非 hermetic 行为的策略。
概览
在给定相同的输入源代码和产品配置的情况下,密闭式构建系统始终会通过将构建与主机系统的更改隔离开来返回相同的输出。
为了隔离 build,密封 build 对本地或远程主机上安装的库和其他软件不敏感。它们依赖于特定版本的 build 工具(例如编译器)和依赖项(例如库)。这使得构建过程自成一体,因为它不依赖于构建环境之外的服务。
密闭性的两个重要方面是:
- 隔离:严密构建系统将工具视为源代码。他们下载工具的副本,并在受管理的文件树中管理其存储空间和使用情况。这样可以在宿主机和本地用户(包括已安装的语言版本)之间实现隔离。
- 源身份:封闭构建系统会尝试确保输入的一致性。Git 等代码库使用唯一的哈希代码来标识一组代码突变。封闭式构建系统使用此哈希来识别构建输入的更改。
优势
密封 build 的主要优势包括:
- 速度:操作的输出可以缓存,除非输入发生变化,否则无需再次运行操作。
- 并行执行:对于给定的输入和输出,构建系统可以构建所有操作的图,以计算高效的并行执行。构建系统会加载规则并计算操作图和哈希输入,以便在缓存中查找。
- 多个 build:您可以在同一台机器上构建多个 hermetic build,每个 build 使用不同的工具和版本。
- 可重现性:封闭构建有助于问题排查,因为您可以确切了解生成相应 build 的条件。
识别非密封性
如果您正准备改用 Bazel,那么提前提高现有 build 的密封性可让迁移更轻松。构建中一些常见的非封闭性来源包括:
.mk
文件中的任意处理- 以非确定性方式创建文件的操作或工具,通常涉及 build ID 或时间戳
- 在不同主机上有所不同的系统二进制文件(例如
/usr/bin
二进制文件、绝对路径、用于原生 C++ 规则自动配置的系统 C++ 编译器) - 在 build 期间写入源代码树。这样可以防止同一源代码树用于其他目标。首次构建会写入源代码树,从而修复目标 A 的源代码树。然后尝试构建目标 B 可能会失败。
排查非密封 build
从本地执行开始,影响本地缓存命中的问题会暴露非密封操作。
- 确保空序列构建:如果您运行
make
并成功构建,再次运行构建时不应重新构建任何目标。如果您在不同系统上或两次运行每个 build 步骤,并比较文件内容的哈希值,发现结果不同,则表示 build 不可重现。 - 运行步骤以调试本地缓存命中,从各种潜在的客户端机器中进行调试,确保您能发现任何客户端环境泄露到操作中的情况。
- 在 Docker 容器中执行构建,该容器仅包含已签出的源代码树和明确的主机工具列表。构建中断和错误消息将捕获隐式系统依赖项。
- 使用远程执行规则发现并修复密封性问题。
- 在每个操作级别启用严格的沙盒,因为 build 中的操作可能是有状态的,并且会影响 build 或输出。
- 工作区规则允许开发者向外部工作区添加依赖项,但它们足够丰富,可让进程中发生任意处理。您可以通过在 Bazel 命令中添加
--experimental_workspace_rules_log_file=PATH
标志,获取 Bazel 工作区规则中一些可能非密封操作的日志。
Bazel 的密封性
如需详细了解其他项目如何成功使用 Bazel 进行密封构建,请参阅以下 BazelCon 讲座:
- 使用 Bazel 构建实时系统 (SpaceX)
- Bazel 远程执行和远程缓存(Uber 和 TwoSigma)
- 通过远程执行和缓存加快构建速度
- 融合 Bazel:更快的增量构建
- 远程执行与本地执行
- 提高远程缓存的易用性 (IBM)
- 使用 Bazel 构建自动驾驶汽车 (BMW)
- 使用 Bazel 构建自动驾驶汽车 + 问答 (GM Cruise)