封闭性

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。
报告问题 查看源代码

本页将介绍封闭性、使用封闭构建的好处,以及用于在构建中找出非封闭行为的策略。

概览

在获得相同的输入源代码和产品配置时,封闭构建系统始终会将 build 与主机系统所做的更改隔离,从而返回相同的输出。

为了隔离构建,封闭构建对安装在本地或远程主机上的库和其他软件敏感。它们依赖于构建工具的特定版本(例如编译器)和依赖项(例如库)。这会使构建流程变得独立,因为它不依赖于构建环境之外的服务。

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

  • 隔离:封闭构建系统将工具视为源代码。他们可以下载工具的副本,并管理其存储空间以及在代管式文件树内的使用。这会使主机和本地用户(包括已安装的语言版本)隔离开来。
  • 源身份:封闭构建系统会尝试确保输入的相同性。代码库(例如 Git)使用唯一的哈希代码标识多组代码变更。封闭式构建系统使用此哈希来标识对构建输入的更改。

优势

封闭 build 的主要优势包括:

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

确定非封闭性

如果您准备改用 Bazel,提前提高现有 build 的封闭性会让迁移变得更轻松。build 中一些非封闭性的常见来源包括:

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

排查非封闭 build 的问题

从本地执行开始,影响本地缓存命中的问题会显示非封闭操作。

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

使用 Bazel 实现封闭性

如需详细了解其他项目如何利用 Bazel 实现封闭构建,请参阅以下 BazelCon 演讲: