锁定文件

锁定文件是一种实现可复现依赖关系的机制,即使在创建了这些依赖关系的新版本或修订时也能实现。让我们通过一个实际例子来看:首先克隆 examples2 仓库

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/versioning/lockfiles/intro

在这个文件夹中,我们有一个小项目,包含 3 个包:一个 matrix 包,模拟某个数学库;一个 engine 包,模拟某个游戏引擎;以及一个 sound32 包,模拟某个 32 位系统的声音库。这些包实际上大部分是空的,它们不构建任何代码,但对于学习锁定文件的概念很有用。

digraph lockfiles { node [fillcolor="lightskyblue", style=filled, shape=box] rankdir="BT" "engine/1.0" -> "matrix/1.0"; "engine/1.0" -> "sound32/1.0" [label="if arch==x86"]; }


我们将首先创建第一个 matrix/1.0 版本

$ conan create matrix --version=1.0

现在我们可以在 engine 文件夹中检查其配方

class Engine(ConanFile):
    name = "engine"
    settings = "arch"

    def requirements(self):
        self.requires("matrix/[>=1.0 <2.0]")
        if self.settings.arch == "x86":
            self.requires("sound32/[>=1.0 <2.0]")

让我们移动到 engine 文件夹并安装其依赖关系

$ cd engine
$ conan install .
...
Requirements
    matrix/1.0#905c3f0babc520684c84127378fefdd0 - Cache
Resolved version ranges
    matrix/[>=1.0 <2.0]: matrix/1.0

由于 matrix/1.0 版本在有效范围内,它被解析并使用。但如果有人创建了新的 matrix/1.11.X 版本,它也会自动被使用,因为它也在有效范围内。为了避免这种情况,我们将通过创建一个 conan.lock 锁定文件来捕获当前依赖关系的“快照”

$ conan lock create .
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278126.0552447"
    ],
    "build_requires": [],
    "python_requires": []
}

我们可以看到创建的 conan.lock 锁定文件包含 matrix/1.0 版本及其修订。但是 sound32/1.0 不在锁定文件中,因为对于默认配置配置文件(非 x86),这个 sound32 不是依赖关系。

现在,创建了一个新的 matrix/1.1 版本

$ cd ..
$ conan create matrix --version=1.1
$ cd engine

看看当我们对引擎发出新的 conan install 命令时会发生什么

$ conan install .
# equivalent to conan install . --lockfile=conan.lock
...
Requirements
   matrix/1.0#905c3f0babc520684c84127378fefdd0 - Cache

正如我们所见,即使 matrix/1.1 在有效范围内,它也没有被使用!这是因为默认情况下,如果找到 conan.lock 文件,将使用 --lockfile=conan.lock 参数。锁定的 matrix/1.0 版本和修订将被用于解析范围,而 matrix/1.1 将被忽略。

同样,可以发出其他 Conan 命令,如果存在 conan.lock,它将被使用

$ conan graph info . --filter=requires # --lockfile=conan.lock is implicit
# display info for matrix/1.0
$ conan create . --version=1.0 # --lockfile=conan.lock is implicit
# creates the engine/1.0 package, using matrix/1.0 as dependency

如果打算使用锁定文件,例如在 CI 中,最好明确指定参数 --lockfile=conan.lock

多配置锁定文件

我们上面看到,enginesound32 包有一个条件依赖,条件是架构为 x86。这也意味着上述锁定文件中没有捕获到 sound32 包的版本。

我们先创建 sound32/1.0 包,然后尝试安装 engine

$ cd ..
$ conan create sound32 --version=1.0
$ cd engine
$ conan install . -s arch=x86 # FAILS!
ERROR: Requirement 'sound32/[>=1.0 <2.0]' not in lockfile

发生这种情况是因为 conan.lock 锁定文件不包含 sound32 的锁定版本。默认情况下,锁定文件是严格的,如果我们要锁定依赖关系,必须在锁定文件内部找到匹配的版本。我们可以通过 --lockfile-partial 参数来放宽这个假设

$ conan install . -s arch=x86 --lockfile-partial
...
Requirements
    matrix/1.0#905c3f0babc520684c84127378fefdd0 - Cache
    sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7 - Cache
Resolved version ranges
    sound32/[>=1.0 <2.0]: sound32/1.0

这将使得部分锁定 matrix/1.0,并像往常一样解析 sound32 版本范围。但我们可以做得更好,我们可以扩展锁定文件,同时锁定 sound32/1.0 版本,以避免新的 sound32 意外版本可能造成的干扰

$ conan lock create . -s arch=x86
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
        "matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
    ],
    "build_requires": [],
    "python_requires": []
}

现在,matrix/1.0sound32/1.0 都被锁定在我们的 conan.lock 锁定文件中。这个锁定文件可以用于两种配置(64位和 x86 架构),锁定文件中包含某个配置不使用的版本不是问题,只要该配置所需的依赖项能在其中找到匹配的版本即可。

重要

锁定文件包含按版本和修订排序的需求列表,因此在针对锁定文件解析时,最新的版本和修订将被优先考虑。锁定文件可以包含同一包的两个或更多不同版本,仅仅是因为不同的版本范围需要它们。排序将提供正确的逻辑,使每个范围都能解析到其有效的版本。

如果锁定文件中的版本不符合有效范围,它将不会被使用。锁定文件不可能强制一个违背 conanfile 所定义需求的依赖关系,因为它们是现有/可实现的依赖图的“快照”,而不能定义“不可能”的依赖图。

锁定文件的演进

即使锁定文件强制并约束了可为一个图解析的版本,这并不意味着锁定文件不能演进。实际上,锁定文件的受控演进对于持续集成等重要流程至关重要,在这种流程中,图中的某个更改的效果需要在与其他可能的并行更改隔离的情况下进行测试。

在本节中,我们将介绍锁定文件的一些基本功能,这些功能允许这种演进。

首先,如果我们现在想在我们的 engine 中引入并测试新的 matrix/1.1 版本,而无需拉取许多可能也获得了新版本的其他依赖项,我们可以手动将 matrix/1.1 添加到锁定文件中

$ Running: conan lock add --requires=matrix/1.1
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
        "matrix/1.1",
        "matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
    ],
    "build_requires": [],
    "python_requires": []
}

需要明确的是:使用 conan lock add 手动添加不一定是推荐的流程,可以通过其他方法自动化此任务,这将在后面解释。这只是对原理和概念的介绍。

重要的思想是,现在我们在锁定文件中获得了两个版本的 matrix,并且 matrix/1.1matrix/1.0 之前,因此对于范围 matrix/[>=1.0 <2.0],第一个 (matrix/1.1) 将被优先考虑。这意味着当现在使用新的锁定文件时,它将解析为 matrix/1.1 版本(即使系统中存在 matrix/1.2 或更高的版本)

$ conan install . -s arch=x86 --lockfile-out=conan.lock
Requirements
    matrix/1.1#905c3f0babc520684c84127378fefdd0 - Cache
    sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7 - Cache
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
        "matrix/1.1#905c3f0babc520684c84127378fefdd0%1675278901.7527816",
        "matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
    ],
    "build_requires": [],
    "python_requires": []
}

请注意,现在 matrix/1.1 已被解析,并且其 revision 也存储在锁定文件中(因为传递了参数 --lockfile-out=conan.lock)。

确实,之前的 matrix/1.0 版本没有被使用。如上所述,锁定文件中存在未使用的旧版本并无害处。但是,如果我们要修剪未使用的版本和修订,可以使用 --lockfile-clean 来实现这一目的

$ conan install . -s arch=x86 --lockfile-out=conan.lock --lockfile-clean
...
Requirements
    matrix/1.1#905c3f0babc520684c84127378fefdd0 - Cache
    sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7 - Cache
...
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
        "matrix/1.1#905c3f0babc520684c84127378fefdd0%1675278901.7527816"
    ],
    "build_requires": [],
    "python_requires": []
}

值得注意的是,-lockfile-clean 可能会移除给定配置中的锁定版本。例如,如果使用的架构是 x86_64 而不是上面的情况,--lockfile-clean 将修剪“未使用”的 sound32,因为在该配置中它未被使用。可以为每个不同的配置评估新的锁定文件,然后合并它们

$ conan lock create . --lockfile-out=64.lock --lockfile-clean
$ conan lock create . -s arch=x86 --lockfile-out=32.lock --lockfile-clean
$ cat 64.lock
{
    "version": "0.5",
    "requires": [
        "matrix/1.1#905c3f0babc520684c84127378fefdd0%1675294635.6049662"
    ],
    "build_requires": [],
    "python_requires": []
}
$ cat 32.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675294637.9775107",
        "matrix/1.1#905c3f0babc520684c84127378fefdd0%1675294635.6049662"
    ],
    "build_requires": [],
    "python_requires": []
}
$ conan lock merge --lockfile=32.lock --lockfile=64.lock --lockfile-out=conan.lock
$ cat conan.lock
{
    "version": "0.5",
    "requires": [
        "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675294637.9775107",
        "matrix/1.1#905c3f0babc520684c84127378fefdd0%1675294635.6049662"
    ],
    "build_requires": [],
    "python_requires": []
}

这种多次清理 + 合并操作不应由开发者完成,而应由 CI 脚本完成,并用于一些将在后面解释的高级 CI 流程。

另请参阅