封闭

报告问题 查看源代码 每夜 build · 8.0 . 7.47.3 · 7.2 · 7.1 · 7.0 · 6.5

本页介绍了密封性、使用密封 build 的好处,以及用于在 build 中识别非密封行为的策略。

概览

给定相同的输入源代码和产品配置时,通过将 build 与主机系统的更改隔离,密封型 build 系统始终会返回相同的输出。

为了隔离 build,密封 build 对本地或远程主机上安装的库和其他软件不敏感。它们依赖于特定版本的构建工具(例如编译器)和依赖项(例如库)。这样一来,构建过程便是自包含的,因为它不依赖于构建环境之外的服务。

密封性的两个重要方面是:

  • 隔离:密封型构建系统会将工具视为源代码。它们会下载工具的副本,并在受管理的文件树中管理其存储和使用。这样,主机和本地用户(包括已安装的语言版本)之间就会隔离。
  • 来源标识:密封型构建系统会尝试确保输入的一致性。代码库(例如 Git)使用唯一的哈希代码来标识一组代码变体。密封型构建系统使用此哈希来识别构建输入的更改。

优势

密封 build 的主要优势包括:

  • 速度:操作的输出可以缓存,除非输入发生变化,否则无需再次运行操作。
  • 并行执行:对于给定的输入和输出,构建系统可以构建所有操作的图表,以计算高效的并行执行。构建系统会加载规则,并计算操作图和哈希输入以在缓存中进行查找。
  • 多个 build:您可以在同一台机器上构建多个密封 build,每个 build 使用不同的工具和版本。
  • 可重现性:封闭式 build 非常适合进行问题排查,因为您知道生成 build 的确切条件。

识别非密封性

如果您准备改用 Bazel,请提前改进现有 build 的密封性,以便更轻松地进行迁移。导致 build 不密封的一些常见原因包括:

  • .mk 文件中的任意处理
  • 以非确定性方式创建文件的操作或工具,通常涉及 build ID 或时间戳
  • 在不同主机上不同的系统二进制文件(例如 /usr/bin 二进制文件、绝对路径、用于原生 C++ 规则自动配置的系统 C++ 编译器)
  • 在构建期间写入源代码树。这样可以防止将同一源代码树用于其他目标。第一个 build 会写入源代码树,修复目标 A 的源代码树。然后,尝试构建目标 B 可能会失败。

排查非容器化 build 问题

从本地执行开始,影响本地缓存命中的各个问题都会揭示非密封操作。

  • 确保 null 顺序 build:如果您运行 make 并成功构建,再次运行 build 不应重新构建任何目标。如果您将每个 build 步骤运行两次或在不同的系统上运行,比较文件内容的哈希值并获得不同的结果,则表示无法重现 build。
  • 运行相应步骤,从各种可能的客户端计算机调试本地缓存命中,以确保您能捕获客户端环境泄露到操作中的任何情况。
  • 在 Docker 容器中执行构建,该容器中除了已检出的源代码树和主机工具的显式列表外,不包含任何其他内容。构建故障和错误消息会捕获隐式系统依赖项。
  • 使用远程执行规则发现并解决密封性问题。
  • 在每个操作级别启用严格的沙盒,因为 build 中的操作可能会具有状态,并会影响 build 或输出。
  • 工作区规则允许开发者向外部工作区添加依赖项,但其功能非常丰富,可允许在此过程中进行任意处理。您可以向 Bazel 命令添加标志 --experimental_workspace_rules_log_file=PATH,以获取 Bazel 工作区规则中某些可能非密封操作的日志。

使用 Bazel 实现密封性

如需详细了解其他项目如何成功使用 Bazel 构建容器化 build,请观看以下 BazelCon 演讲: