天空框架

报告问题 查看来源 每晚 · 7.3。 · 7.2 条 · 7.1 · 7.0。 · 6.5

Bazel 的并行评估和增量模型。

数据模型

数据模型由以下几项组成:

  • SkyValue。也称为节点。SkyValues 是不可变对象, 包含构建过程中构建的所有数据以及 和构建。示例包括:输入文件、输出文件、目标和 目标。
  • SkyKey。不可变的简短名称,用于引用 SkyValue,例如 FILECONTENTS:/tmp/fooPACKAGE://foo
  • SkyFunction。根据节点的键和依赖的节点构建节点。
  • 节点图。一种数据结构,其中包含 节点。
  • Skyframe。增量评估框架 Bazel 的代号为 。

评估

构建是通过评估表示构建请求的节点来实现的。

首先,Bazel 会查找与顶级目录的键相对应的 SkyFunction SkyKey。然后,该函数会请求对所需节点的评估 对顶级节点求值,继而产生其他 SkyFunction 调用, 直至到达叶节点。叶节点通常是代表 输入文件。最后,Bazel 最终将 顶级 SkyValue,存在一些副作用(例如文件中的输出文件) 系统)和节点间依赖关系的有向无环图 参与构建过程。

如果 SkyFunction 无法判断,可以在多次卡券中请求 SkyKeys 推进其工作所需的所有节点。一个简单的例子是评估 输出文件节点就是符号链接:该函数会尝试读取 发现该文件是一个符号链接,从而提取文件系统节点 代表符号链接的目标。但这本身也可能是一个符号链接 在这种情况下,原始函数也需要提取其目标。

在代码中,函数由接口 SkyFunction 和 由名为 SkyFunction.Environment 的接口提供给它的服务。这些 是函数可以执行的操作:

  • 通过调用 env.getValue 请求对另一个节点的求值。如果 表示节点可用,则返回其值,否则返回 null 并且函数本身应返回 null。在后一种情况下, 接着评估依赖节点,然后将原始节点构建器 但这次相同的 env.getValue 调用将返回 非 null 值。
  • 通过调用 env.getValues() 请求评估多个其他节点。 这基本相同,只不过依赖节点 并行评估。
  • 在调用期间执行计算
  • 有副作用,例如将文件写入文件系统。护理需求 两个不同函数可以避免在彼此的方向上 脚趾。一般来说,写入附带效应(数据从 Bazel 向外流出) 没关系,请查看附带效应(即数据流入 Bazel, 已注册的依赖项)不是,因为它们是未注册的依赖项 因此可能导致增量构建不正确。

行为良好的 SkyFunction 实现可避免以任何其他方式访问数据 而不是请求依赖项(例如直接读取文件系统), 因为这会导致 Bazel 无法注册文件的数据依赖关系 这会导致增量构建不正确。

一旦函数有足够的数据来完成其任务,它就应该返回一个非 null 值。

此评估策略具有诸多优势:

  • 保密。如果函数仅通过 那么 Bazel 可以保证,如果输入状态相同, 都会返回相同的数据如果所有星空函数都是确定的,这意味着 整个构建也将具有确定性
  • 正确且完美的增量。如果所有函数的所有输入数据 则 Bazel 只能使需要 在输入数据更改时失效。
  • 并行处理。由于函数只能通过以下方式相互交互: 请求依赖项时,不相互依赖的函数 而 Bazel 可以保证结果与 它们会依序运行。

增量

由于函数只能通过依赖于其他节点来访问输入数据,因此 Bazel 可以构建从输入文件到输出内容的完整数据流图 然后使用这些信息来仅重新构建那些确实需要 要重新构建的:一组已更改的输入文件的反向传递闭包。

特别是,有两种可能的增量策略:自下而上的策略 另一种是“自上而下”模型哪种方式最优,取决于依赖关系图 看起来

  • 在自下而上的失效过程中,在构建图表并且 已知输入值,则所有节点都已失效,这些节点以传递方式依赖于 已更改的文件。如果要构建相同的顶级节点, 。请注意,自下而上的失效需要在所有实例上运行 stat(), 前一版本的输入文件,以确定这些文件是否已更改。本次 使用 inotify 或类似机制来了解 已更改的文件。

  • 在自上而下的失效过程中,顶级节点的传递性关闭 复选框,并且只有传递闭包为干净的那些节点才会保留。 如果节点图很大,但是下一个构建只需要 它的一小部分:自下而上的失效机制会使较大的图表失效 与自上而下失效不同,自上而下失效 第二次构建的示意图

Bazel 只能执行自下而上的失效操作。

为了进一步提高增量,Bazel 会使用“更改剪枝”:如果节点 已失效,但在重新构建时,发现其新值相同 与其旧值一样,则是由于此节点发生更改而失效的节点 可以“复活”

这很有用,例如在更改 C++ 文件中的注释时: 根据该文件生成的 .o 文件是相同的,因此无需调用 重新进行链接。

增量关联 / 编译

此模型的主要限制是,节点的失效 一刀切:当依赖项发生变化时,依赖节点始终 即使存在可以改变的 更改节点的旧值。下面列举了几个例子, 实用:

  • 增量关联
  • 当 JAR 文件中的单个类文件发生更改时,有可能 就地修改 JAR 文件,而不必从头开始重新构建。

Bazel 原则性不支持这些方面的原因 具有双重特征:

  • 效果提升有限。
  • 很难验证更改的结果是否与 Google 重视的基于位的 build 可重复。

到目前为止,通过将 并且通过这种方式实现部分重新评估。例如: 在 Android 应用中,您可以将所有类拆分为多个组,并使用 dex 处理 。这样,如果组中的类保持不变,dexing 会 而无需重复创建

映射到 Bazel 概念

这是对 SkyFunction 键和 SkyValue 键的简要说明 Bazel 用于执行构建的实现:

  • FileStateValue 中的字段。lstat() 的结果。对于现有文件, 函数还会计算其他信息, 文件。这是 Skyframe 图中最低层级的节点, 依赖项
  • FileValue 的返回值。适用于任何关注实际内容或 解析路径取决于相应的 FileStateValue 和 任何需要解析的符号链接(例如 a/bFileValue) 需要 a 的解析路径和 a/b 的解析路径)。通过 区分 FileValueFileStateValue 很重要,因为 如果文件内容不是 实际所需的资源例如,在以下情况下,文件内容不相关 评估文件系统 glob(例如 srcs=glob(["*/*.java"]))。
  • DirectoryListingStateValue 生成的值。readdir() 的结果。点赞 FileStateValue,这是级别最低的节点,没有依赖项。
  • DirectoryListingValue 生成的值。适用于任何关注 条目的 目录。取决于相应的 DirectoryListingStateValue,因为 以及目录的关联 FileValue
  • PackageValue 表示。表示 BUILD 文件的解析版本。取决于 相关 BUILD 文件的 FileValue,也可以传递任何 DirectoryListingValue,用于解析软件包中的 glob (在内部表示 BUILD 文件内容的数据结构)。
  • ConfiguredTargetValue。表示配置的目标(元组) 分析目标过程中生成的一系列操作, 提供给依赖性配置目标的信息。取决于 PackageValue:相应目标所在的;ConfiguredTargetValues 直接依赖项以及表示 build 的特殊节点 配置。
  • ArtifactValue。表示 build 中的文件,无论是源文件还是 输出工件。工件几乎等同于文件,用于 在实际执行构建步骤期间引用文件。源文件 取决于关联节点的 FileValue 以及输出工件 依赖ActionExecutionValue任何可生成 工件。
  • ActionExecutionValue 执行相应的操作。表示操作的执行。取决于 其输入文件的 ArtifactValues。它执行的操作会包含 与 SkyKeys 应采用的概念相反 小。请注意,如果存在以下情况,则不会用到 ActionExecutionValueArtifactValue: 执行阶段不会运行。

这张图作为直观的辅助图 在构建 Bazel 本身之后实现 SkyFunction 实现:

SkyFunction 实现关系图