版本控制简介

到目前为止,我们一直在使用带有固定版本的 requires,例如 requires = "zlib/1.2.12"。但有时依赖关系会演变,新版本会发布,使用者希望尽可能轻松地更新到这些版本。

始终可以编辑 conanfiles 并显式更新到新版本,但 Conan 中存在一些机制,允许在不修改配方的情况下进行此类更新。

版本范围

requires 可以表达对给定软件包的特定版本范围的依赖,语法为 pkgname/[version-range-expression]。让我们看一个例子,请先克隆源代码以重新创建此项目。您可以在 GitHub 的 examples2 仓库中找到它们

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

我们可以看到那里有

conanfile.py
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.8zlib/1.2.11zlib/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。如果现在我们将以下内容添加到配方中

conanfile.py
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

修订版本

当软件包创建者对软件包配方或源代码进行一些更改,但他们没有提升 version 以反映这些更改时会发生什么?Conan 有一个内部机制来跟踪这些修改,它被称为修订版本

配方修订版本是可以与软件包名称和版本一起看到的哈希值,形式为 pkgname/version#recipe_revisionpkgname/version@user/channel#recipe_revision。配方修订版本是配方内容和源代码的哈希值。因此,如果配方、其关联文件或此配方正在打包的源代码中的任何内容发生更改,它将创建一个新的配方修订版本。

您可以使用 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 中显式地固定给定的配方修订版本,例如

def requirements(self):
    self.requires("zlib/1.2.12#87a7211557b6690ef5bf7fc599dd8349")

但是,当创建新的修订版本时,这种机制可能难以维护和更新,因此在一般情况下,可能不应这样做。

锁定文件

版本范围的使用以及在不提升版本的情况下创建给定软件包的新修订版本的可能性,允许进行自动、更快、更方便的更新,而无需编辑配方。

但在某些情况下,还需要提供一组不可变且可重现的依赖关系。此过程称为“锁定”,允许它的机制是“锁定文件”文件。锁定文件是一个文件,其中包含固定的依赖关系列表,指定确切的版本和确切的修订版本。因此,例如,锁定文件永远不会包含带有表达式的版本范围,而只会包含固定的依赖关系。

锁定文件可以看作是某个时间点给定依赖关系图的快照。这样的快照必须是“可实现的”,也就是说,它需要是可以通过 conanfile 配方实际重现的状态。并且即使有新创建的软件包版本,也可以在稍后的时间点使用此锁定文件来强制执行相同的状态。

让我们看看锁定文件的实际应用。首先,让我们将依赖关系固定到示例中的 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 及其确切的修订版本。

另请参阅