产品流水线:构建顺序

上一节我们使用了 `--build=missing` 在同一个 CI 机器上构建所有必需的包。这并非总是期望的,甚至有时不可能,在许多情况下,最好进行分布式构建,以实现更快的构建和更好地利用 CI 资源。最自然的构建负载分配方式是在不同的机器上构建不同的包。让我们看看如何使用 `conan graph build-order` 命令来实现这一点。

让我们像往常一样开始,确保我们有一个干净的环境并定义了正确的仓库。

# First clean the local "build" folder
$ pwd  # should be <path>/examples2/ci/game
$ rm -rf build  # clean the temporary build folder
$ mkdir build && cd build # To put temporary files

$ conan remove "*" -c  # Make sure no packages from last run
# NOTE: The products repo is first, it will have higher priority.
$ conan remote enable products

现在我们暂时忽略 `mapviewer/1.0` 产品,并将本节的重点放在 `game/1.0` 产品上。第一步是计算“构建顺序”,即需要构建的包的列表以及它们的构建顺序。这可以通过以下 `conan graph build-order` 命令完成。

$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe --reduce --format=json > game_build_order.json

请注意以下几点:

  • 必须使用 `--build=missing`,与上一节完全相同。如果未能提供预期的 `--build` 策略和参数,将导致不完整或错误的构建顺序。

  • `--reduce` 参数会消除结果顺序中所有不具有 `binary: Build` 策略的元素。这意味着最终的“构建顺序”不能与其他构建顺序文件合并以聚合到一个文件中,这对于存在多种配置和产品的情况很重要。

  • `--order-by` 参数允许通过“recipe”或“configuration”定义不同的顺序。在此示例中,我们使用 `--order-by=recipe`,旨在按 recipe 并行构建。这意味着,给定包(如 `engine/1.0`)的所有不同二进制文件都应先构建,然后才能构建 `engine/1.0` 的任何消费者。

生成的 `game_build_order.json` 文件如下所示:

game_build_order.json
  {
      "order_by": "recipe",
      "reduced": true,
      "order": [
          [
              {
                  "ref": "engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb",
                  "packages": [
                      [
                          {
                              "package_id": "de738ff5d09f0359b81da17c58256c619814a765",
                              "binary": "Build",
                              "build_args": "--requires=engine/1.0 --build=engine/1.0",
                          }
                      ]
                  ]
              }
          ],
          [
              {
                  "ref": "game/1.0#1715574045610faa2705017c71d0000e",
                  "depends": [
                      "engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb"
                  ],
                  "packages": [
                      [
                          {
                              "package_id": "bac7cd2fe1592075ddc715563984bbe000059d4c",
                              "binary": "Build",
                              "build_args": "--requires=game/1.0 --build=game/1.0",
                          }
                      ]
                  ]
              }
          ]
      ]
  }

为了方便起见,就像 `conan graph info ... --format=html > graph.html` 可以生成一个带有 HTML 交互式依赖图的文件一样,`conan graph build-order ... --format=html > build_order.html` 可以生成上述 JSON 文件的 HTML 可视化表示。

../../_images/build_order_simple.png

生成的 JSON 包含一个 `order` 元素,它是一个列表的列表。这种结构很重要,顶层列表中的每个元素都是一组可以并行构建的包,因为它们之间没有相互依赖关系。您可以将此列表视为一系列“层”,在第 0 层,有不依赖于任何其他正在构建的包的包;在第 1 层,有只包含对第 0 层元素依赖的包,以此类推。

因此,最外层列表中国元素的顺序很重要,必须予以遵守。在构建完一个列表项中的所有包之前,不能开始构建下一个“层”。

利用 `graph_build_order.json` 文件中的信息,可以执行必要包的构建,这与上一节的 `--build=missing` 方式相同,但不是由我们直接管理的。

从 JSON 中获取参数,要执行的命令如下:

$ conan install --requires=engine/1.0 --build=engine/1.0
$ conan install --requires=game/1.0 --build=game/1.0

我们正在手动执行这些命令,但在实践中,这通常会是 CI 中一个循环,该循环遍历 JSON 输出。稍后我们将看到一些 Python 代码来处理这个问题。此时,我们想专注于 `conan graph build-order` 命令,但我们还没有真正解释构建是如何分布的。

另外请注意,在每个元素内部,还有一个内部列表的列表,即 `"packages"` 部分,其中包含针对不同配置必须为特定 recipe 构建的所有二进制文件。

现在让我们继续看如何计算多产品、多配置的构建顺序。