产品流水线:多产品、多配置构建¶
在上一节中,我们计算了一个 conan graph build-order,其中包含一些简化:我们没有考虑 mapviewer 产品,并且只处理了 1 种配置。
在实际场景中,有必要管理多个产品,而最常见的情况是每个产品有多种配置。如果我们按顺序构建这些不同的情况,速度会慢得多且效率低下;如果我们尝试并行构建它们,很容易出现大量重复且不必要的相同包的构建,浪费资源,甚至会因竞态条件或可追溯性问题而产生故障。
为了避免此问题,可以计算一个单一的、统一的“构建顺序”,该顺序聚合了为不同产品和配置计算的所有不同的构建顺序。
我们照例从清理本地缓存和定义正确的仓库开始
# 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
现在,我们将开始计算本教程中正在构建的 2 种不同配置(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.json 和 game_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 配置的 2 个不同二进制文件,可以并行构建。
在实践中,这可能意味着
# 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 来保证依赖项的稳定性。