版本控制简介¶
到目前为止,我们一直使用固定版本的 requires
,例如 requires = "zlib/1.2.12"
。但有时依赖项会演进,新版本发布后,使用者希望尽可能轻松地更新到这些版本。
虽然总是可以编辑 conanfiles
并显式更新版本到新版本,但 Conan 中有一些机制允许在不修改 recipe 的情况下进行此类更新。
版本范围¶
一个 requires
可以使用语法 pkgname/[version-range-expression]
表达对给定包的特定版本范围的依赖。让我们看一个例子。请先克隆源代码以重新创建此项目。你可以在 GitHub 的 examples2 仓库中找到它们。
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/consuming_packages/versioning
我们可以看到那里有
from conan import ConanFile
class CompressorRecipe(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/[~1.2]")
那个 requires
包含表达式 zlib/[~1.2]
,这意味着“大约” 1.2
版本。这意味着它可以解析为 zlib/1.2.8
、zlib/1.2.11
或 zlib/1.2.12
中的任何一个,但不会解析为 zlib/1.3.0
之类的。在所有可用的匹配版本中,版本范围将始终选择最新的一个。
如果我们执行 conan install,我们会看到类似以下内容
$ conan install .
Graph root
conanfile.py: .../conanfile.py
Requirements
zlib/1.2.12#87a7211557b6690ef5bf7fc599dd8349 - Downloaded
Resolved version ranges
zlib/[~1.2]: zlib/1.2.12
如果我们尝试使用 zlib/[<1.2.12]
,这意味着我们希望使用低于 1.2.12
的版本,但该版本被排除,因此满足范围的最新版本将是 zlib/1.2.11
$ conan install .
Resolved version ranges
zlib/[<1.2.12]: zlib/1.2.11
这同样适用于其他类型的要求,例如 tool_requires
。让我们向 recipe 添加一个
from conan import ConanFile
class CompressorRecipe(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/[~1.2]")
def build_requirements(self):
self.tool_requires("cmake/[>3.10]")
我们看到它解析到了最新可用的 CMake 软件包,版本至少为 3.11
$ conan install .
...
Graph root
conanfile.py: .../conanfile.py
Requirements
zlib/1.2.12#87a7211557b6690ef5bf7fc599dd8349 - Cache
Build requirements
cmake/3.22.6#f305019023c2db74d1001c5afa5cf362 - Downloaded
Resolved version ranges
cmake/[>3.10]: cmake/3.22.6
zlib/[~1.2]: zlib/1.2.12
修订版¶
当软件包创建者对软件包 recipe 或源代码进行更改,但他们没有提升 version
以反映这些更改时,会发生什么?Conan 有一个内部机制来跟踪这些修改,它被称为 修订版(revisions)。
Recipe 修订版是以 pkgname/version#recipe_revision
或 pkgname/version@user/channel#recipe_revision
形式与包名和版本一起看到的哈希值。Recipe 修订版是 recipe 内容和源代码的哈希值。因此,如果 recipe、其关联文件或此 recipe 打包的源代码中的任何内容发生变化,都将创建一个新的 recipe 修订版。
您可以使用 conan list 命令列出现有的修订版
$ conan list "zlib/1.2.12#*" -r=conancenter
conancenter
zlib
zlib/1.2.12
revisions
82202701ea360c0863f1db5008067122 (2022-03-29 15:47:45 UTC)
bd533fb124387a214816ab72c8d1df28 (2022-05-09 06:59:58 UTC)
3b9e037ae1c615d045a06c67d88491ae (2022-05-13 13:55:39 UTC)
...
修订版始终解析为最新的(按创建或上传到服务器的时间顺序)修订版。尽管这不是一个常见做法,但可以直接在 conanfile
中显式固定给定的 recipe 修订版,例如:
def requirements(self):
self.requires("zlib/1.2.12#87a7211557b6690ef5bf7fc599dd8349")
然而,当创建新修订版时,这种机制维护和更新起来可能很麻烦,所以在一般情况下,不应该这样做。
锁定文件¶
版本范围的使用,以及在不提升版本的情况下创建给定软件包新修订版的可能性,使得快速、自动和方便的更新成为可能,而无需编辑 recipes。
但在某些情况下,还需要提供一组不可变且可重现的依赖项。此过程被称为“锁定”,而允许此过程的机制是“锁定文件”(lockfile)。锁定文件是一个包含固定依赖项列表的文件,它指定了精确的版本和精确的修订版。因此,例如,锁定文件绝不会包含带有表达式的版本范围,而只包含固定的依赖项。
锁定文件可以看作是某个时间点给定依赖图的快照。这样的快照必须是“可实现的”,也就是说,它需要是从 conanfile recipes 实际可以重现的状态。并且该锁定文件可以在稍后的时间点使用,以强制保持相同的状态,即使有新创建的软件包版本。
让我们看看锁定文件的实际作用。首先,在我们的示例中将依赖项固定到 zlib/1.2.11
def requirements(self):
self.requires("zlib/1.2.11")
然后,我们来捕获一个锁定文件
conan lock create .
-------- Computing dependency graph ----------
Graph root
conanfile.py: .../conanfile.py
Requirements
zlib/1.2.11#4524fcdd41f33e8df88ece6e755a5dcc - Cache
Generated lockfile: .../conan.lock
我们来看看锁定文件 conan.lock
包含什么
{
"version": "0.5",
"requires": [
"zlib/1.2.11#4524fcdd41f33e8df88ece6e755a5dcc%1650538915.154"
],
"build_requires": [],
"python_requires": []
}
现在,让我们恢复原始的 requires
版本范围
def requirements(self):
self.requires("zlib/[~1.2]")
并运行 conan install .,它默认会找到 conan.lock
,并运行等效的 conan install . --lockfile=conan.lock
conan install .
Graph root
conanfile.py: .../conanfile.py
Requirements
zlib/1.2.11#4524fcdd41f33e8df88ece6e755a5dcc - Cache
请注意版本范围是如何不再被解析的,它不会获取 zlib/1.2.12
依赖,即使它在允许的范围 zlib/[~1.2]
内,因为 conan.lock
锁定文件强制它停留在 zlib/1.2.11
以及那个精确的修订版本。
另请参阅