产品管线:带有锁定文件的分布式完整管线¶
本节将介绍多产品、多配置分布式 CI 管线的完整实现。它将涵盖重要的实现细节:
使用锁定文件来确保所有配置的依赖项集保持一致和固定。
将构建好的包上传到
products
仓库。捕获“包列表”并使用它们来运行最终的推广。
如何以编程方式迭代“构建顺序”
像往常一样,我们首先清理本地缓存并定义正确的仓库。
# 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
与我们在 packages pipeline
中所做的一样,当我们想要确保在构建不同配置和产品时依赖项完全相同时,第一步是计算一个 conan.lock
锁定文件,我们可以将它传递给不同的 CI 构建代理,以确保所有地方都使用相同的依赖项集。这可以针对不同的 products
和配置进行增量操作,并将其汇总到最终的单个 conan.lock
锁定文件中。这种方法假设 game/1.0
和 mapviewer/1.0
都将使用相同版本和修订的公共依赖项。
$ conan lock create --requires=game/1.0 --lockfile-out=conan.lock
$ conan lock create --requires=game/1.0 -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock
$ conan lock create --requires=mapviewer/1.0 --lockfile=conan.lock --lockfile-out=conan.lock
$ conan lock create --requires=mapviewer/1.0 -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock
注意
请记住 conan.lock
参数大多是可选的,因为那是默认的锁定文件名称。第一个命令可以输入为 conan lock create --requires=game/1.0
。此外,所有命令,包括 conan install
,如果它们找到现有的 conan.lock
文件,都将自动使用它,而无需明确指定 --lockfile=conan.lock
。本教程中的命令显示得非常完整,是为了教学和完整性考虑。
然后,我们可以计算每个产品和配置的构建顺序。这些命令与上一节中的命令相同,唯一的区别是添加了一个 --lockfile=conan.lock
参数。
$ conan graph build-order --requires=game/1.0 --lockfile=conan.lock --build=missing --order-by=recipe --format=json > game_release.json
$ conan graph build-order --requires=game/1.0 --lockfile=conan.lock --build=missing -s build_type=Debug --order-by=recipe --format=json > game_debug.json
$ conan graph build-order --requires=mapviewer/1.0 --lockfile=conan.lock --build=missing --order-by=recipe --format=json > mapviewer_release.json
$ conan graph build-order --requires=mapviewer/1.0 --lockfile=conan.lock --build=missing -s build_type=Debug --order-by=recipe --format=json > mapviewer_debug.json
同样,build-order-merge
命令将与之前的命令相同。在这种情况下,由于此命令实际上不计算依赖关系图,因此 conan.lock
参数不是必需的,依赖项不会被解析。
$ 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
到目前为止,这个过程与上一节的几乎相同,唯一的区别是捕获和使用了锁定文件。现在,我们将解释 products
管线的“核心”:迭代构建顺序并分发构建,以及收集结果的已构建包。
这将是一个 Python 代码示例,它顺序执行迭代(真实的 CI 系统会将构建并行分发给不同的代理)。
build_order = open("build_order.json", "r").read()
build_order = json.loads(build_order)
to_build = build_order["order"]
pkg_lists = [] # to aggregate the uploaded package-lists
for level in to_build:
for recipe in level: # This could be executed in parallel
ref = recipe["ref"]
# For every ref, multiple binary packages are being built.
# This can be done in parallel too. Often it is for different platforms
# they will need to be distributed to different build agents
for packages_level in recipe["packages"]:
# This could be executed in parallel too
for package in packages_level:
build_args = package["build_args"]
filenames = package["filenames"]
build_type = "-s build_type=Debug" if any("debug" in f for f in filenames) else ""
run(f"conan install {build_args} {build_type} --lockfile=conan.lock --format=json", file_stdout="graph.json")
run("conan list --graph=graph.json --format=json", file_stdout="built.json")
filename = f"uploaded{len(pkg_lists)}.json"
run(f"conan upload -l=built.json -r=products -c --format=json", file_stdout=filename)
pkg_lists.append(filename)
注意
此代码专门用于
--order-by=recipe
构建顺序,如果选择--order-by=configuration
,则 JSON 不同,并且需要不同的迭代。
以下是上述 Python 代码正在执行的任务:
对于构建顺序中的每个
package
,都会发出一个conan install --require=<pkg> --build=<pkg>
命令,并且此命令的结果存储在graph.json
文件中。conan list
命令将此graph.json
转换为一个名为built.json
的包列表。请注意,此包列表实际上存储了已构建的包和必要的传递依赖项。这样做是为了简化,因为稍后这些包列表将用于运行推广,我们也希望推广那些例如ai/1.1.0
的依赖项,它们是在packages pipeline
中构建的,而不是由当前作业构建的。conan upload
命令将包列表上传到products
仓库。请注意,upload
会首先检查仓库中哪些包已经存在,从而避免了如果它们已经存在时的昂贵传输。conan upload
命令的结果被捕获在一个新的包列表中,名为uploaded<index>.json
,我们稍后将累积它,它将用于最终的推广。
实际上,这转换为以下命令(您可以执行这些命令来继续本教程):
# engine/1.0 release
$ conan install --requires=engine/1.0 --build=engine/1.0 --lockfile=conan.lock --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded1.json
# engine/1.0 debug
$ conan install --requires=engine/1.0 --build=engine/1.0 --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded2.json
# game/1.0 release
$ conan install --requires=game/1.0 --build=game/1.0 --lockfile=conan.lock --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded3.json
# game/1.0 debug
$ conan install --requires=game/1.0 --build=game/1.0 --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded4.json
此步骤完成后,新构建的包将位于 products
仓库中,并且我们将有 4 个 uploaded1.json
- uploaded4.json
文件。
简化不同的发布和调试配置,我们的仓库状态将类似于:
现在我们可以将不同的 uploadedX.json
文件累积到一个包含所有内容的单个包列表 uploaded.json
中。
$ conan pkglist merge -l uploaded0.json -l uploaded1.json -l uploaded2.json -l uploaded3.json --format=json > uploaded.json
最后,如果一切顺利,并且我们认为这组新版本和新包二进制文件已准备好供开发人员和其他 CI 作业使用,那么我们可以从 products
仓库推广到 develop
仓库。
# Promotion using Conan download/upload commands
# (slow, can be improved with art:promote custom command)
$ conan download --list=uploaded.json -r=products --format=json > promote.json
$ conan upload --list=promote.json -r=develop -c
我们的最终 develop
仓库状态将是:
这种 develop
仓库的状态将具有以下行为:
开发人员安装
game/1.0
或engine/1.0
时,将默认解析并使用最新的ai/1.1.0
。他们还将找到依赖项的预编译二进制文件,并且可以使用最新的依赖项集继续开发。之前使用锁定文件锁定
ai/1.0
版本的开发人员和 CI 仍然能够继续使用该依赖项,而不会造成任何破坏,因为新版本和包二进制文件不会破坏或使先前存在的二进制文件失效。
此时,可能会出现关于 CI 中使用的锁定文件如何处理的问题。请注意,conan.lock
现在包含了锁定的 ai/1.1.0
版本。可以有不同的策略,例如将此锁定文件存储在“products” Git 仓库中,以便开发人员检出这些仓库时轻松获取。然而请注意,此锁定文件与 develop
仓库的最新状态匹配,因此,开发人员检出其中一个“products” Git 仓库并针对 develop
服务器仓库执行 conan install
时,将自然地解析到锁定文件中存储的相同依赖项。
如果“产品”以某种方式(例如安装程序、debian/rpm/choco/等包)进行捆绑,则最好至少将此锁定文件存储在任何发布包中,以便为软件的最终用户附带或包含用于生成该发布包的锁定文件,这样无论开发仓库中发生什么变化,这些锁定文件都可以在以后从发布信息中恢复。
最终说明¶
正如本 CI 教程引言中所述,这并非旨在成为一个万能药,也不是一个您可以直接部署在组织中的 CI 系统。到目前为止,本教程展示了开发人员持续集成过程的“理想路径”,以及他们的包更改(这些包是更大产品的一部分)如何作为这些产品的一部分进行测试和验证。
本 CI 教程的重点是介绍一些重要的概念、良好实践和工具,例如:
定义组织“产品”的重要性,这些产品是需要针对开发人员创建的新依赖项版本进行检查和构建的主要可交付成果。
开发人员的新依赖项版本在验证之前不应上传到主开发仓库,以避免破坏其他开发人员和 CI 作业。
如何使用多个仓库构建一个 CI 管线,以隔离未经验证的更改和新版本。
如何使用
conan graph build-order
在 CI 中高效构建大型依赖关系图,以及如何将不同配置和产品的构建顺序合并在一起。为什么
lockfiles
在存在并发 CI 构建时是 CI 中必需的。版本控制的重要性,以及
package_id
在大型依赖关系图中仅重新构建必要部分的作用。不使用
user/channel
作为在整个 CI 管线中变化的包的可变和动态限定符,而是使用不同的服务器仓库。在新包版本验证后,跨服务器仓库运行包推广(复制)。
本教程尚未涵盖许多实现细节、策略、用例和错误场景,例如:
如何集成需要新的破坏性主要版本号的包的破坏性更改。
不同的版本控制策略,在某些情况下使用预发布版本、版本号或依赖配方修订。
锁定文件如何在不同构建之间存储和使用,以及是否最好持久化它们以及存储在哪里。
不同的分支和合并策略、每夜构建、发布流程。
我们计划扩展本 CI 教程,包括更多示例和用例。如果您有任何问题或反馈,请在 https://github.com/conan-io/conan/issues 提交工单。