跳到主要内容

版本控制入门

在清单文件中使用版本控制

我们从创建一个依赖 fmtzlib 的简单 CMake 项目开始。

创建一个文件夹,并添加以下文件:

kmpkg.json

{
"name": "versions-test",
"version": "1.0.0",
"dependencies": [
{
"name": "fmt",
"version>=": "7.1.3#1"
},
"zlib"
],
"builtin-baseline": "3426db05b996481ca31e95fff3734cf23e0f51bc"
}

main.cpp

#include <fmt/core.h>
#include <zlib.h>

int main()
{
fmt::print("fmt 版本为 {}\n"
"zlib 版本为 {}\n",
FMT_VERSION, ZLIB_VERSION);
return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.18)

project(versionstest CXX)

add_executable(main main.cpp)

find_package(ZLIB REQUIRED)
find_package(fmt CONFIG REQUIRED)
target_link_libraries(main PRIVATE ZLIB::ZLIB fmt::fmt)

接下来,我们通过 CMake 构建并运行项目:

  1. 为项目创建构建目录。

    PS D:\versions-test> mkdir build
    PS D:\versions-test> cd build
  2. 配置 CMake。

    PS D:\versions-test\build> cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:/kmpkg/scripts/buildsystems/kmpkg.cmake ..
    -- Running kmpkg install
    Detecting compiler hash for triplet x86-windows...
    The following packages will be built and installed:
    fmt[core]:x64-windows -> 7.1.3#1 -- D:\Work\viromer\kmpkg\buildtrees\versioning\versions\fmt\4f8427eb0bd40da1856d4e67bde39a4fda689d72
    kmpkg-cmake[core]:x64-windows -> 2021-02-26 -- D:\Work\viromer\kmpkg\buildtrees\versioning\versions\kmpkg-cmake\51896aa8073adb5c8450daa423d03eedf0dfc61f
    kmpkg-cmake-config[core]:x64-windows -> 2021-02-26 -- D:\Work\viromer\kmpkg\buildtrees\versioning\versions\kmpkg-cmake-config\d255b3d566a8861dcc99a958240463e678528066
    zlib[core]:x64-windows -> 1.2.11#9 -- D:\Work\viromer\kmpkg\buildtrees\versioning\versions\zlib\827111046e37c98153d9d82bb6fa4183b6d728e4
    ...
  3. 构建项目。

    PS D:\versions-test\build> cmake --build .
    [2/2] Linking CXX executable main.exe
  4. 运行程序!

    PS D:\versions-test\build> ./main.exe
    fmt 版本为 70103
    zlib 版本为 1.2.11

查看输出内容:

fmt[core]:x86-windows -> 7.1.3#1 -- D:\kmpkg\buildtrees\versioning\versions\fmt\4f8427eb0bd40da1856d4e67bde39a4fda689d72
...
zlib[core]:x86-windows -> 1.2.11#9 -- D:\kmpkg\buildtrees\versioning\versions\zlib\827111046e37c98153d9d82bb6fa4183b6d728e4

kmpkg 并未使用 ports/ 目录下的端口文件,而是从 buildtrees/versioning/versions/ 目录中检出对应版本的文件。在经典模式下运行 kmpkg 时,仍会使用 ports/ 目录中的文件。

信息

仅当使用 CMake 3.18 或更高版本时,配置 CMake 过程中才能看到 kmpkg 的输出。若使用旧版本 CMake,可查看构建目录中的 kmpkg-manifest-install.log 文件。

阅读我们的清单文件公告博客,了解如何在 MSBuild 项目中使用清单文件。

清单文件的变更

如果你之前使用过清单文件,会发现新增了一些 JSON 属性。我们来梳理这些变更:

version

{
"name": "versions-test",
"version": "1.0.0"
}

这是项目的版本声明。此前,只能通过 version-string 属性为项目声明版本。随着版本控制功能的推出,kmpkg 支持了更多新的版本命名规范。

版本规范说明
version点分隔的数字格式:1.0.0.5
version-semver符合 语义化版本 规范:1.2.01.2.0-rc
version-dateYYYY-MM-DD 格式的日期:2021-01-01
version-string任意字符串:vistacandy

version>=

{
"dependencies": [
{ "name": "fmt", "version>=": "7.1.3" },
"zlib"
]
}

该属性用于声明最低版本约束,仅允许在 "dependencies" 声明中使用。在示例中,我们为 fmt 显式设置了 7.1.3#1 版本的最低约束。

若传递依赖要求更高版本,kmpkg 会升级该约束。例如,若 zlib 声明依赖 fmt 7.1.4 版本,则 kmpkg 会安装 7.1.4 版本。

kmpkg 采用“最低版本优先”策略——在示例中,即便 fmt 8.0.0 版本已发布,kmpkg 仍会安装 7.1.3#1 版本(因为这是满足约束的最低版本)。这种策略的优势在于:更新 kmpkg 时不会出现意外的依赖升级,且只要使用相同的清单文件,就能保证构建的版本可复现。

若需升级依赖,可提高最低版本约束,或使用更新的基线。

builtin-baseline

{ "builtin-baseline": "3426db05b996481ca31e95fff3734cf23e0f51bc" }

该字段为所有端口声明版本控制基线。启用版本控制功能必须设置基线,否则会使用 ports/ 目录中的最新版本。你可以运行 git rev-parse HEAD 命令获取 kmpkg 当前提交的哈希值,并将其设为 builtin-baseline。更多信息请参阅 "builtin-baseline" 文档

在示例中,我们未为 zlib 声明版本约束,其版本由基线决定。kmpkg 会在 3426db05b996481ca31e95fff3734cf23e0f51bc 这个提交记录中,查找该时间点 zlib 的最新版本(本例中为 1.2.11#9)。

版本解析过程中,基线版本会被视为最低版本约束。若显式声明的约束低于基线版本,该显式约束会被升级至基线版本。

例如,若我们修改依赖声明如下:

{ "dependencies": [
{
"name": "fmt",
"version>=": "7.1.3#1"
},
{
"name": "zlib",
"version>=": "1.2.11#7"
}
] }
信息

1.2.11#7 表示版本 1.2.11,端口版本(port version)为 7

由于基线为 zlib 设定的最低版本约束是 1.2.11#9,而更高版本满足 1.2.11#7 的最低版本约束,因此 kmpkg 会将其升级。

基线也是批量升级多个版本的便捷方式——例如,若你依赖多个 boost 库,只需设置一次基线,而非为每个包单独声明版本约束。

但如果想要固定一个低于基线的版本,该怎么做?

overrides

由于基线为所有包设定了版本下限,且显式约束低于基线时会被升级,因此需要另一种机制来降级版本至基线以下。

kmpkg 为此提供的机制是 overrides(覆盖项)。当为某个包声明覆盖项后,kmpkg 会忽略所有其他版本约束(无论是清单文件中直接声明的,还是来自传递依赖的)。简而言之,overrides 会强制 kmpkg 使用声明的精确版本,无例外。

我们再次修改示例,强制 kmpkg 使用 fmt 6.0.0 版本:

{
"name": "versions-test",
"version": "1.0.0",
"dependencies": [
{
"name": "fmt",
"version>=": "7.1.3#1"
},
{
"name": "zlib",
"version>=": "1.2.11#7"
}
],
"builtin-baseline": "3426db05b996481ca31e95fff3734cf23e0f51bc",
"overrides": [
{
"name": "fmt",
"version": "6.0.0"
}
]
}

重新构建项目:

PS D:\versions-test\build> rm ./CMakeCache.txt
PS D:\versions-test\build> rm -r ./kmpkg_installed
PS D:\versions-test\build> cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:/kmpkg/scripts/buildsystems/kmpkg.cmake ..
-- Running kmpkg install
Detecting compiler hash for triplet x86-windows...
The following packages will be built and installed:
fmt[core]:x86-windows -> 6.0.0 -- D:\kmpkg\buildtrees\versioning\versions\fmt\d99b6a35e1406ba6b6e09d719bebd086f83ed5f3
zlib[core]:x86-windows -> 1.2.11#9 -- D:\kmpkg\buildtrees\versioning\versions\zlib\827111046e37c98153d9d82bb6fa4183b6d728e4
...
PS D:\versions-test\build> cmake --build .
[2/2] Linking CXX executable main.exe

运行程序:

PS D:\versions-test\build> .\main.exe
fmt 版本为 60000
zlib 版本为 1.2.11

可以看到,fmt 版本已按预期变为 6.0.0。

版本控制与自定义端口

最后需要说明的是,覆盖端口(overlay ports)与版本解析的交互逻辑:二者无交互。

具体来说,当你为某个端口提供覆盖版本时,kmpkg 会始终使用该覆盖端口,而不关心其中包含的版本。原因有两点:(1) 这与覆盖端口“完全屏蔽原有端口”的既有行为保持一致;(2) 覆盖端口无需(也不要求)提供足够信息来支持 kmpkg 的版本控制功能。

若你希望同时实现灵活的端口自定义和版本控制,应考虑创建自定义仓库

扩展阅读

若你想深入了解版本控制的工作原理,建议阅读 版本控制参考文档版本控制概念文档