锁定文件¶
Lockfiles 是一种实现可重复依赖的机制,即使在创建新版本或修订版本时也是如此。让我们通过一个实际示例来了解它,首先克隆 examples2 仓库
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/versioning/lockfiles/intro
在此文件夹中,我们有一个小型项目,由 3 个包组成:一个 matrix 包,模拟一些数学库,一个 engine 包,模拟一些游戏引擎,以及一个 sound32 包,模拟一些 32 位系统的声音库。这些包实际上大部分是空的,它们不构建任何代码,但它们有助于学习 lockfiles 的概念。
我们将从创建第一个 matrix/1.0 版本开始
$ conan create matrix --version=1.0
现在我们可以在 engine 文件夹中查看其 recipe
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.1 或 1.X 版本,它也会被自动使用,因为它也在有效范围内。为了避免这种情况,我们将通过创建 conan.lock lockfile 来捕获当前依赖项的“快照”。
$ conan lock create .
$ cat conan.lock
{
"version": "0.5",
"requires": [
"matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278126.0552447"
],
"build_requires": [],
"python_requires": []
}
我们可以看到创建的 conan.lock lockfile 包含 matrix/1.0 版本及其修订版本。但是 sound32/1.0 不在 lockfile 中,因为对于默认配置配置文件(非 x86),此 sound32 并不是依赖项。
现在,创建了一个新的 matrix/1.1 版本
$ cd ..
$ conan create matrix --version=1.1
$ cd engine
并查看当我们为 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
如果打算使用 lockfile(例如在 CI 中),则最好明确指定 --lockfile=conan.lock 参数。
多配置 lockfiles¶
我们在上面看到 engine 依赖于 sound32 包,前提是架构为 x86。这也意味着该 sound32 包版本未包含在上述 lockfile 中。
让我们先创建 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 lockfile 不包含 sound32 的锁定版本。默认情况下,lockfiles 是严格的,如果我们正在锁定依赖项,则必须在 lockfile 中找到匹配的版本。我们可以使用 --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 版本范围。但我们可以做得更好,我们可以扩展我们的 lockfile 以锁定 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.0 和 sound32/1.0 都被锁定在我们的 conan.lock lockfile 中。可以使用此 lockfile 来处理两种配置(64 位和 x86 架构),lockfile 中存在某个配置未使用的版本不是问题,只要该配置所需的依赖项能在其中找到匹配的版本即可。
重要
Lockfiles 包含按版本和修订版本排序的需求列表,因此在解析 lockfile 时,最新的版本和修订版本是优先使用的。一个 lockfile 可以包含同一包的两个或更多不同版本,仅仅因为不同的版本范围需要它们。排序将提供正确的逻辑,以便每个范围解析到每个有效版本。
如果 lockfile 中的版本不适合有效范围,则不会使用它。lockfiles 不能强制执行与 conanfile 定义的要求相悖的依赖项,因为它们是现有/可实现依赖项图的“快照”,但不能定义“不可能”的依赖项图。
演进 lockfiles¶
即使 lockfiles 强制执行和约束了图表可解析的版本,也不意味着 lockfiles 不能演进。实际上,lockfiles 的受控演进对于持续集成等重要流程至关重要,此时希望隔离测试图中一个更改的效果,避免与其他可能的并发更改的影响。
在本节中,我们将介绍 lockfiles 的一些基本功能,这些功能允许这种演进。
首先,如果我们现在想在我们的 engine 中引入并测试新的 matrix/1.1 版本,而不必拉入许多可能已经获得新版本的其他依赖项,我们可以手动将 matrix/1.1 添加到 lockfile 中。
$ 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` 手动添加,可以通过其他方法自动完成此任务,稍后将进行解释。这只是对原则和概念的介绍。
重要的想法是,现在我们在 lockfile 中有了 matrix 的 2 个版本,并且 matrix/1.1 在 matrix/1.0 之前,因此对于范围 matrix/[>=1.0 <2.0],将优先使用第一个版本(matrix/1.1)。这意味着当我们现在使用新的 lockfile 时,它将解析到 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 中(因为将 --lockfile-out=conan.lock 作为参数传递)。
确实,以前的 matrix/1.0 版本没有被使用。如上所述,lockfile 中存在未使用的旧版本没有害处。但是,如果我们想清理未使用的版本和修订版本,我们可以使用 --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`,因为在该配置中它未被使用。可以为每个不同的配置评估新的 lockfiles,然后合并它们。
$ 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 流。
另请参阅