产品流水线:多产品多配置构建

在前一节中,我们计算了一个 conan graph build-order,其中包含了几处简化:我们没有考虑 mapviewer 产品,并且只处理了一个配置。

在实际场景中,需要管理多个产品,并且最常见的情况是每个产品都有不止一种配置。如果按顺序构建这些不同的情况,速度会慢得多且效率低下;如果尝试并行构建它们,很容易导致大量重复且不必要的相同包构建,浪费资源,甚至可能产生竞态条件或可追溯性问题。

为避免此问题,可以计算一个单一的、统一的“构建顺序”,该顺序聚合了为不同产品和配置计算的所有不同的构建顺序。

像往常一样,我们先清理本地缓存并定义正确的仓库。

# 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

现在,我们将开始计算本教程中用于构建的两种不同配置(debug 和 release)的 game/1.0 的构建顺序。

$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe --format=json > game_release.json
$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe -s build_type=Debug --format=json > game_debug.json

这些命令基本上与上一节相同,每个命令使用不同的配置并创建不同的输出文件:game_release.jsongame_debug.json。这些文件将与之前的文件类似,但由于我们没有使用 --reduce 参数(这很重要!),它们实际上将包含图中所有元素的“构建顺序”,即使其中一些只包含 binary: Build 定义,而另一些则包含 binary: Download|Cache|etc

现在,让我们计算 mapviewer/1.0 的构建顺序。

$ conan graph build-order --requires=mapviewer/1.0 --build=missing --order-by=recipe --format=json > mapviewer_release.json
$ conan graph build-order --requires=mapviewer/1.0 --build=missing --order-by=recipe -s build_type=Debug --format=json > mapviewer_debug.json

请注意,在生成的 mapviewer_xxx.json 构建顺序文件中,对于 mapviewer/1.0 将只有一个包含 binary: Download 的元素,因为实际上没有其他包需要构建;而且,由于 mapviewer 是一个静态链接的应用程序,Conan 知道它可以“跳过”其依赖项的二进制文件。如果我们使用了 --reduce 参数,我们将得到一个空的 order。但这并不是一个问题,因为下一步的最终步骤将真正计算需要构建的内容。

让我们将所有 4 个不同的“构建顺序”文件(2 个产品 x 每个产品 2 个配置)合并在一起。

$ conan graph build-order-merge --file=game_release.json --file=game_debug.json --file=mapviewer_release.json --file=mapviewer_debug.json --reduce --format=json > build_order.json

现在我们已经应用了 --reduce 参数,生成了一个最终的 build_order.json,该文件已准备好分发给构建代理,并且只包含那些需要构建的特定包。

{
    "order_by": "recipe",
    "reduced": true,
    "order": [
        [
            {
                "ref": "engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb",
                "packages": [
                    [
                        {
                            "package_id": "de738ff5d09f0359b81da17c58256c619814a765",
                            "filenames": ["game_release"],
                            "build_args": "--requires=engine/1.0 --build=engine/1.0",
                        },
                        {
                            "package_id": "cbeb3ac76e3d890c630dae5c068bc178e538b090",
                            "filenames": ["game_debug"],
                            "build_args": "--requires=engine/1.0 --build=engine/1.0",

                        }
                    ]
                ]
            }
        ],
        [
            {
                "ref": "game/1.0#1715574045610faa2705017c71d0000e",
                "packages": [
                    [
                        {
                            "package_id": "bac7cd2fe1592075ddc715563984bbe000059d4c",
                            "filenames": ["game_release"],
                            "build_args": "--requires=game/1.0 --build=game/1.0",
                        },
                        {
                            "package_id": "01fbc27d2c156886244dafd0804eef1fff13440b",
                            "filenames": ["game_debug"],
                            "build_args": "--requires=game/1.0 --build=game/1.0",
                        }
                    ]
                ]
            }
        ]
    ],
    "profiles": {
        "game_release": {"args": ""},
        "game_debug": {"args": "-s:h=\"build_type=Debug\""},
        "mapviewer_release": {"args": ""},
        "mapviewer_debug": {"args": "-s:h=\"build_type=Debug\""}
    }
}

此构建顺序总结了必要的构建。首先,需要构建 engine/1.0 的所有不同二进制文件。此配方包含 2 个不同的二进制文件,一个用于 Release,另一个用于 Debug。这些二进制文件属于 packages 列表中的同一个元素,这意味着它们之间没有依赖关系,可以并行构建。每个二进制文件都通过 "filenames": ["game_release"], 跟踪其原始的构建顺序文件,因此可以推断出需要应用于它的配置文件。 build_order.json 文件包含一个 profiles 部分,有助于恢复用于创建相应原始构建顺序文件的配置文件和设置命令行参数。

然后,在构建完 engine/1.0 的所有二进制文件后,就可以继续构建 game/1.0 的不同二进制文件。它也包含两个用于 debug 和 release 配置的二进制文件,可以并行构建。

实际上,这可能意味着:

# This 2 could be executed in parallel
# (in different machines, or different Conan caches)
$ conan install --requires=engine/1.0 --build=engine/1.0
$ conan install --requires=engine/1.0 --build=engine/1.0 -s build_type=Debug

# Once engine/1.0 builds finish, it is possible
# to build these 2 binaries in parallel (in different machines or caches)
$ conan install --requires=game/1.0 --build=game/1.0
$ conan install --requires=game/1.0 --build=game/1.0 -s build_type=Debug

在本节中,我们仍然省略了一些重要的实现细节,这些细节将在后续章节中介绍。目标是专注于 conan graph build-order-merge 命令,以及如何将不同的产品和配置合并到一个“构建顺序”中。下一节将更详细地展示如何在 CI 中实际分发此构建顺序,使用 lockfiles 来保证一致的依赖关系。