当前位置: 首页 > news >正文

C++包管理工具:conan2持续集成 (CI) 教程

1.持续集成 (CI) ​

  • 这是一个高级主题,需要具备 Conan 的基础知识。请先阅读并练习用户教程。
  • 本节面向设计和实施涉及 Conan 包的生产 CI 管道的 DevOps 和构建工程师。如果不是这种情况,您可以跳过本节。

持续集成 (CI) 对不同用户和组织有不同的含义。在本教程中,我们将涵盖用户更改其包的源代码并希望自动为这些包构建新二进制文件,以及计算这些新包更改是否能够顺利集成或破坏组织主要产品的场景。

在本教程中,我们将使用这个小型项目,该项目使用多个包(默认是静态库)来构建两个应用程序:一个视频游戏和一个地图查看器实用程序。游戏和地图查看器是我们的最终“产品”,即分发给用户的内容:

game/1.0 -> engine/1.0 -> (ai/1.0, graphics/1.0, mathlib/1.0)
mapviewer/1.0 -> graphics/1.0

依赖图中的所有包都使用版本范围声明其直接依赖关系。例如,game 包含 requires("engine/[>=1.0 <2]"),因此依赖项的新的补丁和次要版本将自动被使用,而无需修改配方。

注意:重要说明

  • 本节是动手实践教程。旨在通过复制命令在您的机器上重现。
  • 本教程展示了解决 CI 问题的一些工具、良好实践和常用方法。但这并非唯一的方式。不同的组织可能有不同的需求和优先级、不同的构建服务能力和预算、不同的规模等。所介绍的原则和实践可能需要调整。
  • 如有任何问题或反馈,请在 https://github.com/conan-io/conan/issues 提交新工单。
  • 然而,一些原则和最佳实践对所有方法都是通用的。例如,包的不可变性、使用仓库升级(promotions)而不是使用通道(channel)来实现此目的等都是应遵循的良好实践。

1.1 包与产品管道

当开发人员对某个包的源代码进行更改时,我们将整个系统的 CI 视为由两个不同的部分或管道组成:包管道产品管道

  • 包管道 (Packages Pipeline):负责在单个包的代码更改时构建该包。如果需要,它会为不同的配置(平台、架构、构建类型等)构建它。
  • 产品管道 (Products Pipeline):负责构建组织的主要“产品”(实现最终应用程序或可交付成果的包),并确保依赖项中的更改和新版本能够正确集成,必要时重建依赖图中的任何中间包。

思路是,如果某个开发人员对 ai 包进行了更改,产生了新的 ai/1.1.0 版本,包管道将首先构建这个新版本。但这个新版本可能会意外破坏或需要重建某些消费者包。如果我们的组织主要产品是 game/1.0mapviewer/1.0,那么产品管道可以被触发,在这种情况下,它将重建受更改影响的 engine/1.0game/1.0,而所有其他包将保持不变。

1.2 仓库与升级 (Repositories and Promotions)

多个服务器端仓库的概念对于 CI 非常重要。在本教程中,我们将使用 3 个仓库:

  • develop:这是开发人员在其机器上配置的主要仓库,用于 conan install 依赖项并进行工作。因此,它预期相当稳定,类似于 git 中的共享“develop”分支,并且该仓库应包含组织预定义平台的预编译二进制文件,这样开发人员和 CI 就不需要反复 --build=missing 并从源代码构建。
  • packages:此仓库将用于临时上传由“包管道”构建的包,避免直接上传到 develop 仓库造成干扰,直到这些包完全验证通过。
  • products:此仓库将用于临时上传由“产品管道”构建的包,同时构建和测试新的依赖项更改不会破坏主要“产品”。

升级 (Promotions) 是使包从一个管道可用于另一个管道的机制。将上述包管道和产品管道与仓库连接起来,将有两个升级步骤:

  • 当包管道为单个包构建了所有不同配置的所有二进制文件并上传到 packages 仓库后,该包的新版本和更改可以被视为“正确”并升级(复制)到 products 仓库。
  • 当产品管道已从源代码构建了所有需要重新构建的包(因为 products 仓库中有新的包版本),并检查了组织的“产品”(如 game/1.0mapviewer/1.0)没有被破坏后,这些包就可以从 products 仓库升级(复制)到 develop 仓库,供所有其他开发人员和 CI 使用。

注意:

  • 不可变性 (Immutability) 在包管理和 DevOps 中很重要。强烈反对修改通道 (channel) 来实现升级,请参阅包升级。
  • 版本控制方法很重要。本教程将遵循默认的 Conan 版本控制方法,详情请参阅此处。

本教程仅模拟开发流程。在生产系统中,还会有其他仓库和升级步骤,例如用于 QA 团队的测试仓库,以及最终用户的发布仓库,这样包在通过验证后可以从 develop 升级到 testing 再到 release。有关升级的更多信息,请阅读包升级。

让我们开始教程,进入下一节进行项目设置:

1.2.1 项目设置 (Project setup)

本教程所需的代码位于 examples2 仓库中,克隆它并进入文件夹:

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/ci/game

服务器仓库设置 (Server repositories setup)

我们需要在同一个服务器上有 3 个不同的仓库。请确保您有一个正在运行的 Artifactory 实例可用。您可以从下载页面下载免费的 Artifactory CE 并在自己的计算机上运行,或者使用 Docker:

$ docker run --name artifactory -d -p 8081:8081 -p 8082:8082 releases-docker.jfrog.io/jfrog/artifactory-cpp-ce:7.63.12
# 可以用 "docker stop artifactory" 停止

启动后,您可以访问 http://localhost:8081/ 查看(用户:“admin”,密码:“password”)。如果您有另一个可用的 Artifactory,也可以使用,前提是您可以在那里创建新仓库。

第一步,登录 Web UI 并创建 3 个不同的本地仓库,分别命名为 developpackagesproducts

然后,根据 project_setup.py 文件,需要配置以下环境变量。请定义 ARTIFACTORY_URLARTIFACTORY_USER 和/或 ARTIFACTORY_PASSWORD(如果需要)以适应您的设置:

# TODO: 这必须由用户配置
SERVER_URL = os.environ.get("ARTIFACTORY_URL", "http://localhost:8081/artifactory/api/conan")
USER = os.environ.get("ARTIFACTORY_USER", "admin")
PASSWORD = os.environ.get("ARTIFACTORY_PASSWORD", "password")

初始依赖图 (Initial dependency graph)

警告:

  • 项目的初始化将删除服务器上 developproductspackages 三个仓库的内容。
  • examples2/ci/game 文件夹包含一个 .conanrc 文件,该文件定义了一个本地缓存,因此在本教程中执行的命令不会污染或更改您的主 Conan 缓存。
$ python project_setup.py

这将执行几个任务:清理服务器仓库,为依赖图创建初始的 Debug 和 Release 二进制文件,将它们上传到 develop 仓库,然后清理本地缓存。请注意,在此示例中,我们为了方便使用 Debug 和 Release 作为不同的配置,但在实际情况中,这些将是不同的配置,例如 Windows/X86_64、Linux/x86_64、Linux/armv8 等,并在不同的计算机上运行。

设置完成后,可以检查到定义了 3 个远程仓库,但只有 develop 远程是启用的,并且本地缓存中没有包:

$ conan remote list
products: http://localhost:8081/artifactory/api/conan/products [Verify SSL: True, Enabled: False]
develop: http://localhost:8081/artifactory/api/conan/develop [Verify SSL: True, Enabled: True]
packages: http://localhost:8081/artifactory/api/conan/packages [Verify SSL: True, Enabled: False]$ conan list *
Found 0 pkg/version recipes matching * in local cache
Local Cache
WARN: There are no matching recipe references

重要提示: 远程仓库的顺序很重要。如果启用了 products 仓库,它将比 develop 具有更高的优先级,因此如果它包含新版本,将从那里获取。

服务器 develop 仓库中的这个依赖图是我们教程的起点,被假定为项目的功能性和稳定的“开发”状态,开发人员可以 conan install 以在任何不同的包上工作。

1.2.2 包管道 (Packages Pipeline)

包管道负责在开发人员向组织仓库的源代码提交更改时,为不同的配置和平台构建、创建和上传包的二进制文件。例如,如果开发人员对 ai 包进行了一些更改,改进了库的某些功能,并将版本提升到 ai/1.1.0。如果组织需要支持 Windows 和 Linux 平台,那么包管道将在认为更改有效之前为新的 ai/1.1.0 构建 Windows 和 Linux 的二进制文件。如果某些配置在特定平台下构建失败,通常认为更改无效并停止处理这些更改,直到代码被修复。

对于包管道,我们将从 ai 配方中的简单源代码更改开始,模拟对 ai 包的改进,为我们的游戏提供更好的算法。

让我们对 ai 包进行以下更改:

  1. 更改 ai/src/ai.cpp 函数的实现,将消息从 Some Artificial 改为 SUPER BETTER Artificial
  2. ai/include/ai.h 中的默认 intelligence=0 值更改为新的 intelligence=50
  3. 最后,提升版本号。由于我们对包的公共头文件进行了更改,建议提升次要版本号,因此让我们编辑 ai/conanfile.py 文件并将 version = "1.1.0" 定义在那里(而不是之前的 1.0)。请注意,如果我们对 ai 公共 API 进行了破坏性更改,建议改为更改主版本号并创建新的 2.0 版本。

包管道将负责为新的 ai/1.1.0 构建不同的包二进制文件,并将它们上传到 packages 二进制仓库,以避免干扰或对其他开发人员和 CI 作业造成潜在问题。

如果管道成功,它将把这些包升级(复制)到 products 二进制仓库,否则停止。

在构建 ai/1.1.0 的这些二进制包时,需要考虑几个方面。以下教程小节以递增的复杂性解释了相同的工作。

注意 所有命令都可以在仓库的 run_example.py 文件中找到。该文件主要供维护者和测试使用,但在出现问题时可能作为参考。

包管道:单一配置 (Package pipeline: single configuration)

我们将从最简单的情况开始,即我们只需要构建 1 种配置,并且该配置可以在当前的 CI 机器上构建。

正如我们在介绍不同服务器二进制仓库时所描述的,想法是包构建默认只使用 develop 仓库,该仓库对于开发人员和 CI 作业被认为是稳定的。

此管道从干净状态开始,缓存中没有包,并且只启用了 develop 仓库。

在这种配置下,CI 作业可以简单地执行:

$ cd ai
$ conan create . --build="missing:ai/*"
...
ai/1.1.0: SUPER BETTER Artificial Intelligence for aliens (Release)!
ai/1.1.0: Intelligence level=50

注意 --build="missing:ai/*" 在某些情况下可能不是完全必要的,但在其他情况下可以节省时间。例如,如果开发人员只更改了仓库的 README,根本没有提升版本号,Conan 将不会生成新的配方修订版,并将其检测为无操作,从而避免不必要地从源代码重建二进制文件。

如果我们处于单一配置场景并且构建正确,对于这种简单情况,我们不需要升级,只需将构建的包直接上传到 products 仓库就足够了,产品管道稍后会获取它。

# 我们不想干扰开发人员或 CI,上传到 products
$ conan remote enable products
$ conan upload "ai*" -r=products -c
$ conan remote disable products

这是一个非常简单的场景,让我们转向更现实的场景:需要构建多个配置。

包管道:多配置 (Package pipeline: multi configuration)

在上一节中,我们只构建了 1 种配置。本节将涵盖需要构建超过 1 种配置的情况。为了方便起见,我们将在这里使用 Release 和 Debug 配置,但在实际情况下,这些配置更像是 Windows、Linux、OSX,为不同的架构构建,交叉构建等。

让我们开始清理缓存:

$ conan remove "*" -c # 确保没有上次运行的包

我们将在计算机上顺序创建 2 种配置的包,但请注意这些通常会在不同的计算机上运行,因此 CI 系统通常会并行启动不同配置的构建。

# Release 构建 (Listing 1)
$ cd ai # 如果尚未在 "ai" 文件夹内
$ conan create . --build="missing:ai/*" -s build_type=Release --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_release.json
$ conan remote disable packages# Debug 构建 (Listing 2)
$ conan create . --build="missing:ai/*" -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_debug.json
$ conan remote disable packages

当 Release 和 Debug 配置都成功完成后,我们将在仓库中有这些包:

ai/1.1.0 的所有不同二进制文件都已正确构建后,包管道可以认为其工作成功,并决定升级这些二进制文件。但需要进一步的包构建和检查,因此包管道不是将它们升级到 develop 仓库,而是可以将它们升级到 products 二进制仓库。由于所有其他开发人员和 CI 都使用 develop 仓库,在此阶段也不会破坏任何人:

# 从 packages 升级到 product (Listing 3)
# 聚合包列表
$ conan pkglist merge -l uploaded_release.json -l uploaded_debug.json --format=json > uploaded.json
$ conan remote enable packages
$ conan remote enable products
# 使用 Conan download/upload 命令进行升级
# (慢,可以使用 art:promote 自定义命令改进)
$ conan download --list=uploaded.json -r=packages --format=json > promote.json
$ conan upload --list=promote.json -r=products -c
$ conan remote disable packages
$ conan remote disable products

第一步使用 conan pkglist merge 命令将“Release”和“Debug”配置的包列表合并为一个 uploaded.json 包列表。此列表将用于运行升级。

在此示例中,我们使用了缓慢的 conan download + conan upload 升级方式。使用 conan art:promote 扩展命令可以更高效。

运行升级后,服务器中将有以下包:

总结:

  • 我们构建了 2 种不同的配置,Release 和 Debug(可以是 Windows/Linux 或其他),并将它们上传到 packages 仓库。
  • 当所有配置的所有包二进制文件都成功构建后,我们将它们从 packages 升级到 products 仓库,使它们可用于产品管道。
  • 在包创建过程中捕获了包列表,并合并为一个列表来运行升级。

我们还没有考虑的一个方面是,在构建过程中 ai/1.1.0 的依赖项可能会发生变化。转到下一节,了解如何使用锁定文件实现更一致的多配置构建。

包管道:使用锁定文件的多配置 (Package pipeline: multi configuration using lockfiles)

在之前的示例中,我们为 ai/1.1.0 构建了 Debug 和 Release 包二进制文件。在现实世界场景中,要构建的二进制文件将用于不同的平台(Windows、Linux、嵌入式),不同的架构,并且通常无法在同一台机器上构建,需要不同的计算机。

前面的示例有一个重要的假设:ai/1.1.0 的依赖项在构建过程中完全不会改变。在许多场景中,这个假设不成立,例如,如果有任何其他并发的 CI 作业,并且一个成功的作业在 develop 仓库中发布了一个新的 mathlib/1.1 版本。

那么有可能 ai/1.1.0 的一个构建,例如在 Linux 服务器上运行的较早开始,使用先前的 mathlib/1.0 版本作为依赖项,而 Windows 服务器稍后启动,它们的构建将使用最近的 mathlib/1.1 版本作为依赖项。这是一个非常不希望的情况,同一个 ai/1.1.0 版本的二进制文件使用了不同的依赖项版本。这可能导致后续的图解析问题,或者更糟的是,发布后不同平台的行为不同。

避免这种依赖项差异的方法是强制使用相同的依赖项版本和修订版,这可以通过使用锁定文件 (lockfiles) 来实现。

创建和应用锁定文件相对简单。创建和升级配置的过程将与上一节相同,只是应用了锁定文件。

创建锁定文件 (Creating the lockfile)

让我们像往常一样确保我们从干净的状态开始:

$ conan remove "*" -c # 确保没有上次运行的包

然后我们可以创建锁定文件 conan.lock

# 为 Release 配置捕获一个锁定文件
$ conan lock create . -s build_type=Release --lockfile-out=conan.lock
# 扩展锁定文件以覆盖 Debug 配置(如果存在特定于 Debug 的依赖项)
$ conan lock create . -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock

请注意,不同的配置、使用不同的配置文件或设置可能会导致不同的依赖图。一个锁定文件可用于锁定不同的配置,但重要的是要迭代不同的配置/配置文件并将它们的信息捕获到锁定文件中。

注意: conan.lock 是默认参数,如果存在 conan.lock 文件,它可能会被 conan install/create 和其他图命令自动使用。这可以简化许多命令,但本教程为了清晰和教学原因展示了完整的显式命令。

conan.lock 文件可以检查,它将类似于:

{"version": "0.5","requires": ["mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea%1724319985.398"],"build_requires": [],"python_requires": [],"config_requires": []
}

如我们所见,它锁定了 mathlib/1.0 依赖项的版本和修订版。

有了锁定文件,创建不同的配置完全相同,但向 conan create 步骤提供 --lockfile=conan.lock 参数,它将保证无论是否存在新的 mathlib/1.1 版本或新的修订版,都将始终使用确切的 mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea 依赖项。以下构建可以并行启动但在不同时间执行,它们仍将始终使用相同的 mathlib/1.0 依赖项:

# Release 构建 (Listing 4)
$ cd ai # 如果尚未在 "ai" 文件夹内
$ conan create . --build="missing:ai/*" --lockfile=conan.lock -s build_type=Release --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_release.json
$ conan remote disable packages# Debug 构建 (Listing 5)
$ conan create . --build="missing:ai/*" --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_debug.json
$ conan remote disable packages

注意与前一示例的唯一修改是添加了 --lockfile=conan.lock。升级也将与之前相同:

# 从 packages 升级到 product (Listing 6)
# 聚合包列表
$ conan pkglist merge -l uploaded_release.json -l uploaded_debug.json --format=json > uploaded.json
$ conan remote enable packages
$ conan remote enable products
# 使用 Conan download/upload 命令进行升级
# (慢,可以使用 art:promote 自定义命令改进)
$ conan download --list=uploaded.json -r=packages --format=json > promote.json
$ conan upload --list=promote.json -r=products -c
$ conan remote disable packages
$ conan remote disable products

最终结果将与上一节相同,但这次保证了 Debug 和 Release 二进制文件都是使用完全相同的 mathlib 版本构建的:

现在我们在 products 仓库中有了新的 ai/1.1.0 二进制文件,我们可以认为包管道已完成,并进入下一节,构建和检查我们的产品,看看这个新的 ai/1.1.0 版本是否能正确集成。

1.2.3 产品管道 (Products Pipeline)

产品管道回答了一个更具挑战性的问题:我的“产品”是否能用包的新版本正确构建?以及它们的依赖项?这是真正的“持续集成”部分,其中不同包中的更改会针对组织重要的产品进行测试,以检查它们是否能干净地集成或破坏。

让我们继续上面的例子,如果我们现在有一个新的 ai/1.1.0 包,它会破坏现有的 game/1.0 和/或 mapviewer/1.0 应用程序吗?是否需要从源代码重新构建那些直接或间接依赖 ai 包的现有包?在本教程中,我们将 game/1.0mapviewer/1.0 视为我们的“产品”,但稍后将进一步解释这个概念,特别是为什么从“产品”角度思考很重要,而不是试图在 CI 中显式地自上而下建模依赖关系。

在我们的示例中,这个产品管道的本质是,上传到 products 仓库的新 ai/1.1.0 版本会自动落入定义的有效版本范围内,并且我们的版本控制方法意味着这样的次要版本提升将需要从源代码构建其消费者,在本例中是 engine/1.0game/1.0,并且按照特定的顺序,而所有其他包将保持不变。知道哪些包需要从源代码构建以及按什么顺序,并执行该构建以检查主要组织产品是否仍然能使用新的依赖项版本正常工作,是产品管道的责任。

什么是产品 (What are the products)

产品是组织(公司、团队、项目)交付的最终软件产物,为这些产物的用户提供一些价值。在这个例子中,我们将 game/1.0mapviewer/1.0 视为“产品”。请注意,可以定义同一包的不同版本作为产品,例如,如果我们必须为不同客户维护不同版本的游戏,我们可以有 game/1.0game/2.3 以及 mapviewer 的不同版本作为产品。

“产品”方法除了关注业务价值的优势外,还有另一个非常重要的优势:它避免了在 CI 层对依赖图进行建模。一个常见的尝试是尝试建模反向依赖模型,即在 CI 级别表示给定包的依赖者或消费者。在我们的示例中,如果我们为构建 ai 包配置了一个作业,我们可以有另一个用于 engine 包的作业,该作业在 ai 作业之后触发,以某种方式在 CI 系统中配置这种拓扑结构。

但是这种方法根本无法扩展,并且有非常重要的限制:

  • 上面的例子相对简单,但实际上依赖图可以有更多的包,甚至几百个,这使得在 CI 中定义所有包之间的依赖关系非常繁琐且容易出错。
  • 依赖关系随时间演变,使用新版本,一些依赖关系被移除,新的依赖关系被添加。在 CI 级别建模的仓库之间的简单关系可能导致非常低效、缓慢且耗时的 CI,如果不是脆弱的并且由于某些依赖关系更改而不断中断的话。
  • 发生在依赖图下游的组合性质,一个相对稳定的顶层依赖,例如 mathlib/1.0 可能被多个消费者使用,如 ai/1.0ai/1.1ai/1.2,而每个消费者又可能被多个 engine 不同版本使用,依此类推。仅构建消费者的最新版本在许多情况下是不够的,而构建所有版本的成本将极其高昂。
  • “反向”依赖模型,即询问给定包的“依赖者”在实践中极具挑战性,特别是在像 Conan 这样的去中心化方法中,包可以存储在不同的仓库中,包括不同的服务器,并且没有所有包及其关系的中央数据库。此外,“反向”依赖模型与直接模型类似,是有条件的。由于依赖关系可以基于任何配置(设置、选项)进行条件化,反向也受限于相同的逻辑,并且这种逻辑也随着每个新的修订版和版本而演变和变化。

在 C 和 C++ 项目中,由于编译模型涉及头文件文本包含成为消费者二进制产物的一部分,以及原生产物链接模型,“产品”管道变得比其他语言更加必要和关键。

构建中间包的新二进制文件 (Building intermediate packages new binaries)

一个经常被问到的问题是,当消费者包针对新的依赖项版本构建时,它的版本会是什么。明确地说明我们的例子,我们定义需要重新构建 engine/1.0 包,因为它现在依赖于新的 ai/1.1.0 版本:

  • 我们应该创建一个新的 engine/1.1 版本来构建以支持新的 ai/1.1.0 吗?
  • 或者我们应该保留 engine/1.0 版本?

答案在于二进制模型以及依赖项如何影响 package_id。Conan 有一个二进制模型,它同时考虑了依赖项的版本、修订版和 package_id,以及不同的包类型(package_type 属性)。

建议是将包版本与源代码保持一致。如果 engine/1.0 是从其源代码仓库的特定提交/标签构建的,并且该仓库的源代码根本没有更改,那么拥有一个偏离源代码的更改包版本会非常令人困惑。使用 Conan 二进制模型,我们将为 engine/1.0 拥有 2 个不同的二进制文件,具有 2 个不同的 package_id。一个二进制文件将针对 ai/1.0 版本构建,另一个二进制文件将针对 ai/1.1.0 构建,类似于:

$ conan list engine:* -r=develop
engine/1.0
revisions
fba6659c9dd04a4bbdc7a375f22143cb (2024-08-22 09:46:24 UTC)
packages
2c5842e5aa3ed21b74ed7d8a0a637eb89068916e
info
settings
...
requires
ai/1.0.Z
graphics/1.0.Z
mathlib/1.0.Z
de738ff5d09f0359b81da17c58256c619814a765
info
settings
...
requires
ai/1.1.Z
graphics/1.0.Z
mathlib/1.0.Z

让我们看看产品管道如何构建这样的 engine/1.0game/1.0 新二进制文件,使用新的依赖项版本。在以下小节中,我们将以增量方式呈现一个产品管道,与包管道相同。

产品管道:单一配置 (Products pipeline: single configuration)

在本节中,我们将实现一个非常基础的产品管道,不进行分布式构建,不使用锁定文件或构建多个配置。

主要思想是说明需要重新构建一些包,因为有一个新的 ai/1.1.0 版本可以被我们的主要产品集成。这个新的 ai 版本在 products 仓库中,因为它已经被“包管道”成功构建。让我们首先确保我们有一个干净的环境,并定义了正确的仓库:

# 首先清理本地 "build" 文件夹
$ pwd # 应该是 <path>/examples2/ci/game
$ rm -rf build # 清理临时构建文件夹
$ mkdir build && cd build # 存放临时文件
# 现在清理包并定义远程仓库
$ conan remove "*" -c # 确保没有上次运行的包
# 注意:products 仓库优先,具有更高优先级。
$ conan remote enable products

回想一下,products 仓库比 develop 仓库具有更高的优先级。这意味着 Conan 将首先在 products 仓库中解析,如果它找到为定义的版本范围有效的版本,它将停止在那里并返回该版本,而不会检查 develop 仓库(使用 --update 可以检查所有仓库,但速度较慢,并且使用正确的仓库排序,这不是必需的)。

正如我们已经定义的,我们的主要产品是 game/1.0mapviewer/1.0,让我们首先尝试安装和使用 mapviewer/1.0

$ conan install --requires=mapviewer/1.0
...
Requirements
graphics/1.0#24b395ba17da96288766cc83accc98f5 - Downloaded (develop)
mapviewer/1.0#c4660fde083a1d581ac554e8a026d4ea - Downloaded (develop)
mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea - Downloaded (develop)
...
Install finished successfully
# 激活环境并运行可执行文件
# 在 Windows 上使用 "conanbuild.bat && mapviewer"
$ source conanrun.sh && mapviewer
...
graphics/1.0: Checking if things collide (Release)!
mapviewer/1.0:serving the game (Release)!

如我们所见,mapviewer/1.0 根本不依赖于 ai 包,任何版本都不依赖。因此,如果我们安装它,我们已经有一个预编译的二进制文件,一切正常。

但如果我们现在对 game/1.0 尝试同样的操作:

$ conan install --requires=game/1.0
...
======== Computing necessary packages ========
...
ERROR: Missing binary: game/1.0:bac7cd2fe1592075ddc715563984bbe000059d4c
game/1.0: WARN: Cant find a game/1.0 package binary bac7cd2fe1592075ddc715563984bbe000059d4c for the configuration:
...
[requires]
ai/1.1.0#01a885b003190704f7617f8c13baa630

它会失败,因为它将从 products 仓库获取 ai/1.1.0,并且没有针对这个新 ai 版本的 game/1.0 预编译二进制文件。这是正确的,ai 是一个静态库,因此我们需要针对它重新构建 game/1.0,让我们使用 --build=missing 参数来执行:

$ conan install --requires=game/1.0 --build=missing
...
======== Computing necessary packages ========
Requirements
ai/1.1.0:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3 - Download (products)
engine/1.0:de738ff5d09f0359b81da17c58256c619814a765 - Build
game/1.0:bac7cd2fe1592075ddc715563984bbe000059d4c - Build
graphics/1.0:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3 - Download (develop)
mathlib/1.0:4d8ab52ebb49f51e63d5193ed580b5a7672e23d5 - Download (develop)
-------- Installing package engine/1.0 (4 of 5) --------
engine/1.0: Building from source
...
engine/1.0: Package de738ff5d09f0359b81da17c58256c619814a765 created
-------- Installing package game/1.0 (5 of 5) --------
game/1.0: Building from source
...
game/1.0: Package bac7cd2fe1592075ddc715563984bbe000059d4c created
Install finished successfully

注意 --build=missing 知道 engine/1.0 也需要一个新的二进制文件,这是因为它依赖于新的 ai/1.1.0 版本的结果。然后,Conan 以正确的顺序继续构建包,首先必须构建 engine/1.0,因为 game/1.0 依赖于它。构建后,我们可以列出新构建的二进制文件,并查看它们如何依赖于新版本:

$ conan list engine:*
Local Cache
engine
engine/1.0
revisions
fba6659c9dd04a4bbdc7a375f22143cb (2024-09-30 12:19:54 UTC)
packages
de738ff5d09f0359b81da17c58256c619814a765
info
...
requires
ai/1.1.Z
graphics/1.0.Z
mathlib/1.0.Z$ conan list game:*
Local Cache
game
game/1.0
revisions
1715574045610faa2705017c71d0000e (2024-09-30 12:19:55 UTC)
packages
bac7cd2fe1592075ddc715563984bbe000059d4c
info
...
requires
ai/1.1.0#01a885b003190704f7617f8c13baa630:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3
engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb:de738ff5d09f0359b81da17c58256c619814a765
graphics/1.0#24b395ba17da96288766cc83accc98f5:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3
mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea:4d8ab52ebb49f51e63d5193ed580b5a7672e23d5

新的 engine/1.0:de738ff5d09f0359b81da17c58256c619814a765 二进制文件依赖于 ai/1.1.Z,因为它是一个静态库,它只需要为次要版本更改重新构建,而不需要为补丁版本重新构建。而新的 game/1.0 二进制文件将依赖于完整的精确 ai/1.1.0#revision:package_id,并且也依赖于依赖于 ai/1.1.Z 的新的 engine/1.0:de738ff5d09f0359b81da17c58256c619814a765 新二进制文件。

现在可以运行游戏了:

# 激活环境并运行可执行文件
# 在 Windows 上使用 "conanbuild.bat && game"
$ source conanrun.sh && game
mathlib/1.0: mathlib maths (Release)!
ai/1.1.0: SUPER BETTER Artificial Intelligence for aliens (Release)!
ai/1.1.0: Intelligence level=50
graphics/1.0: Checking if things collide (Release)!
engine/1.0: Computing some game things (Release)!
game/1.0:fun game (Release)!

我们可以看到新的 game/1.0 二进制文件包含了 ai/1.1.0 的改进,并且正确地与 engine/1.0 的新二进制文件链接。

这是一个基本的“产品管道”,我们设法在必要时构建和测试我们的主要产品(回想一下 mapviewer 并没有真正受到影响,因此根本不需要重建)。通常,生产“产品管道”会上传构建的包到仓库并运行新的升级到 develop 仓库。但由于这是一个非常基础和简单的管道,让我们稍等片刻,继续更高级的场景。

产品管道:构建顺序 (Products pipeline: the build-order)

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

让我们像往常一样确保我们有一个定义了正确仓库的干净环境:

# 首先清理本地 "build" 文件夹
$ pwd # 应该是 <path>/examples2/ci/game
$ rm -rf build # 清理临时构建文件夹
$ mkdir build && cd build # 存放临时文件
$ conan remove "*" -c # 确保没有上次运行的包
# 注意:products 仓库优先,具有更高优先级。
$ 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,旨在并行化每个配方的构建,这意味着对于给定包(如 engine/1.0)的所有可能的不同二进制文件都应该首先构建,然后才能构建 engine/1.0 的任何消费者。

生成的 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 可视化表示。

生成的 json 包含一个 order 元素,它是一个列表的列表。这种安排很重要,顶层列表中的每个元素是一组可以并行构建的包,因为它们之间没有任何关系。您可以将此列表视为“级别 (levels)”列表,在级别 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 中会有一个 for 循环来执行 json 输出。此时我们想重点介绍 conan graph build-order 命令,但我们还没有真正解释构建是如何分布的。

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

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

产品管道:多产品多配置构建 (Products pipeline: multi-product multi-configuration builds)

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

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

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

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

# 首先清理本地 "build" 文件夹
$ pwd # 应该是 <path>/examples2/ci/game
$ rm -rf build # 清理临时构建文件夹
$ mkdir build && cd build # 存放临时文件
$ conan remove "*" -c # 确保没有上次运行的包
# 注意:products 仓库优先,具有更高优先级。
$ conan remote enable products

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

$ 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 参数,我们将得到一个空的顺序。但这并不是问题,因为下一个最终步骤将真正计算需要构建的内容。

让我们获取所有 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 个不同的二进制文件,可以并行构建。

在实践中,这意味着类似以下内容:

# 这两个可以并行执行
# (在不同的机器上,或不同的 Conan 缓存中)
$ conan install --requires=engine/1.0 --build=engine/1.0
$ conan install --requires=engine/1.0 --build=engine/1.0 -s build_type=Debug# 一旦 engine/1.0 构建完成,
# 就可以并行构建这两个二进制文件(在不同的机器或缓存中)
$ 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 命令以及如何将不同的产品和配置合并到一个“构建顺序”中。下一节将更详细地展示如何使用锁定文件保证依赖关系恒定,从而真正分发此构建顺序。

产品管道:使用锁定文件的分布式完整管道 (Products pipeline: distributed full pipeline with lockfiles)

本节将展示多产品、多配置分布式 CI 管道的完整实现。它将涵盖重要的实现细节:

  • 使用锁定文件保证所有配置具有一致且固定的依赖关系集。
  • 将构建的包上传到 products 仓库。
  • 捕获“包列表”并使用它们运行最终升级。
  • 如何以编程方式迭代“构建顺序”。

让我们像往常一样开始清理本地缓存并定义正确的仓库:

# 首先清理本地 "build" 文件夹
$ pwd # 应该是 <path>/examples2/ci/game
$ rm -rf build # 清理临时构建文件夹
$ mkdir build && cd build # 存放临时文件
$ conan remove "*" -c # 确保没有上次运行的包
# 注意:products 仓库优先,具有更高优先级。
$ conan remote enable products

与我们在包管道中为确保在构建不同配置和产品时依赖关系完全相同类似,第一步是计算一个 conan.lock 锁定文件,我们可以将其传递给不同的 CI 构建代理以强制执行相同的依赖关系集。这可以针对不同的产品和配置增量完成,最终聚合到单个 conan.lock 锁定文件中。这种方法假设 game/1.0mapviewer/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

到目前为止,这个过程几乎与上一节相同,只是捕获并使用了锁定文件。现在,我们将解释产品管道的“核心”:迭代构建顺序、分发构建以及收集生成的构建包。

这将是一些 Python 代码的示例,该代码顺序执行迭代(真实的 CI 系统会将构建分发到不同的代理并行执行):

build_order = open("build_order.json", "r").read()
build_order = json.loads(build_order)
to_build = build_order["order"]
pkg_lists = [] # 用于聚合上传的包列表for level in to_build:for recipe in level: # 这可以并行执行ref = recipe["ref"]# 对于每个 ref,正在构建多个二进制包。# 这也可以并行完成。通常针对不同平台,# 它们需要分发到不同的构建代理for packages_level in recipe["packages"]:# 这也可以并行执行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 代码正在执行以下任务:

  • 对于构建顺序中的每个包,发出一个 conan install --require=<pkg> --build=<pkg> 命令,并将此命令的结果存储在 graph.json 文件中。
  • conan list 命令将此 graph.json 转换为名为 built.json 的包列表。请注意,此包列表实际上存储了构建的包和必要的传递依赖项。这样做是为了简单起见,因为稍后这些包列表将用于运行升级,并且我们也希望升级在包管道中构建的依赖项,如 ai/1.1.0,而不是由该作业构建的。
  • conan upload 命令将包列表上传到 products 仓库。请注意,上传首先检查仓库中已存在哪些包,如果它们已经存在,则避免代价高昂的传输。
  • 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 文件。

简化不同的 release 和 debug 配置,我们仓库的状态将类似于:

[图示:仓库状态示意图,显示新构建的 engine 和 game 的 release/debug 二进制文件在 products 仓库中]

我们现在可以将不同的 uploadedX.json 文件累积到一个包含所有内容的单个包列表 uploaded.json 中:

$ conan pkglist merge -l uploaded0.json -l uploaded1.json -l uploaded2.json -l uploaded3.json --format=json > uploaded.json

最后,如果一切顺利,并且我们认为这套新版本和新包二进制文件已准备好供开发人员和其他 CI 作业使用,那么我们可以运行从 productsdevelop 仓库的最终升级:

# 从 products 升级到 develop (Listing 8)
# 使用 Conan download/upload 命令进行升级
# (慢,可以使用 art:promote 自定义命令改进)
$ conan download --list=uploaded.json -r=products --format=json > promote.json
$ conan upload --list=promote.json -r=develop -c

我们最终的 develop 仓库状态将是:

[图示:仓库状态示意图,显示所有包(包括新构建的 engine 和 game)都在 develop 仓库中]

这个 develop 仓库的状态将具有以下行为:

  • 安装 game/1.0engine/1.0 的开发人员默认将解析到最新的 ai/1.1.0 并使用它。他们将找到依赖项的预编译二进制文件,并且可以继续使用最新的依赖项集进行开发。
  • 使用锁定文件锁定 ai/1.0 版本的开发人员和 CI 仍然能够继续工作,而不会破坏任何东西,因为新的版本和包二进制文件不会破坏或使先前存在的二进制文件失效。

此时,可能会提出如何处理 CI 中使用的锁定文件的问题。请注意,conan.lock 现在包含锁定的 ai/1.1.0 版本。可能有不同的策略,例如将此锁定文件存储在“产品”的 git 仓库中,使开发人员在检出这些仓库时易于使用。然而,请注意,此锁定文件匹配 develop 仓库的最新状态,因此检出其中一个“产品”git 仓库的开发人员对 develop 服务器仓库执行 conan install 将自然解析到锁定文件中存储的相同依赖项。

至少在发布捆绑包中包含此锁定文件是一个好主意,如果“产品”以某种方式捆绑(安装程序、debian/rpm/choco/等包),则包含或附加到此捆绑发布以供最终用户使用,使用的锁定文件,这样无论开发仓库发生什么变化,以后都可以从发布信息中恢复这些锁定文件。

最终说明 (Final remarks)

正如本 CI 教程介绍中所述,这并不打算成为您可以在组织中即插即用的“银弹”CI 系统。到目前为止,本教程展示了一个开发人员的“快乐路径”持续集成过程,以及他们作为更大产品一部分的包中的更改如何作为这些产品的一部分进行测试和验证。

本 CI 教程的重点是介绍一些重要概念、良好实践和工具,例如:

  • 定义组织“产品”的重要性,即需要针对新创建的依赖项版本进行检查和构建的主要可交付成果。
  • 开发人员的新依赖项版本在验证之前不应上传到主开发仓库,以免破坏其他开发人员和 CI 作业。
  • 如何使用多个仓库构建 CI 管道,以隔离未经验证的更改和新版本。
  • 如何使用 conan graph build-order 高效构建大型依赖图,以及如何将不同配置和产品的构建顺序合并在一起。
  • 为什么在存在并发 CI 构建时需要锁定文件。
  • 版本控制的重要性,以及 package_id 在大型依赖图中仅重建必要内容的作用。
  • 不在 CI 管道中使用 user/channel 作为变量的动态限定符,而是使用不同的服务器仓库。
  • 当新包版本通过验证时,跨服务器仓库运行包升级(promotions)。

仍然有许多实现细节、策略、用例和错误场景尚未在本教程中涵盖:

  • 如何集成需要新的破坏性主版本的包的破坏性更改。
  • 不同的版本控制策略,使用预发布版本,在特定情况下依赖版本或配方修订版。
  • 锁定文件如何存储和跨不同构建使用,是否适合持久化它们以及在哪里持久化。
  • 不同的分支和合并策略、夜间构建、发布流程。

我们计划扩展此 CI 教程,包括更多示例和用例。如果您有任何问题或反馈,请在 https://github.com/conan-io/conan/issues 创建工单。

http://www.lqws.cn/news/553069.html

相关文章:

  • 给自己网站增加一个免费的AI助手,纯HTML
  • 广外计算机网络期末复习
  • (LeetCode 每日一题) 2099. 找到和最大的长度为 K 的子序列 (排序)
  • VScode使用usb转网口远程开发rk3588
  • 展开说说:Android之ContentProvider源码浅析
  • 【安卓Sensor框架-1】SensorService 的启动流程
  • PMO 与IPD、CMMI、项目管理什么区别和联系
  • Yolo11模型训练速通
  • 【C语言】超市管理系统丨完整源码与实现解析
  • python的医疗废弃物收运管理系统
  • 设计模式之桥接模式(Java)-JDBC也实现了桥接模式
  • 分布式电源采集控制装置:山东光伏电站的“智能中枢”
  • RK3568-drm框架
  • NLP中的同义词替换及我踩的坑
  • Element Plus el-button实例类型详解
  • 短波监测设备和超短波监测设备的区别
  • 磁悬浮支撑:从实验室到工业应用的挑战与机遇
  • 数据结构:最小生成树—Prim(普里姆)与Kruskal(克鲁斯卡尔)算法
  • UNION 和 UNION ALL
  • 回调函数、作用域与闭包:从图片预览案例深入理解
  • 文件管理与Java操作全解析
  • 编译安装detectron2
  • 常用工具库
  • 北大肖臻《区块链技术与应用》学习笔记
  • 智能库室管控系统DW-S306|全国已经规模化应用
  • 微服务项目,启动某服务,编译后就没反应
  • 【JS-6-ES6中的let和const】深入理解ES6中的let和const:块级作用域与变量声明的新范式
  • 【数据标注师】意图标注
  • 力扣网C语言编程题:在数组中查找目标值位置之二分查找法
  • 能否仅用两台服务器实现集群的高可用性??