跳到主要内容

版本控制概念

最小版本选择

kmpkg 采用最小版本选择策略进行版本管理,该策略受 Go 语言版本管理方式 启发,并在部分细节上做了调整:

  • 始终从全新安装开始,无需执行升级/降级操作;
  • 通过引入“基线(baseline)”机制,支持无约束依赖项。

但核心的最小选择原则保持不变:给定一组版本约束,kmpkg 会选择能满足所有约束的、“最旧”的包版本。

采用最小版本策略有以下优势:

  • 行为可预测,易于理解;
  • 版本升级由用户主动控制,不会在新版本发布时自动升级;
  • 无需使用 SAT 求解器。

举个例子,假设有如下包依赖图:

    (A 1.0) -> (B 1.0)

(A 1.1) -> (B 1.0)
-> (C 3.0)

(A 1.2) -> (B 2.0)
-> (C 3.0)

(C 2.0)

以及如下清单文件:

{
"name": "example",
"version": "1.0.0",
"dependencies": [
{ "name": "A", "version>=": "1.1" },
{ "name": "C", "version>=": "2.0" }
],
"builtin-baseline": "<某一 Git 提交记录,其中 A 的基线版本为 1.0>"
}

计入传递依赖后,最终的约束集合如下:

  • A >= 1.1
    • B >= 1.0
    • C >= 3.0
  • C >= 2.0

由于 kmpkg 必须满足所有约束,最终安装的包集合为:

  • A 1.1:尽管存在 A 1.2,但约束仅要求版本 ≥ 1.1,因此 kmpkg 选择满足条件的最小版本;
  • B 1.0:由 A 1.1 传递依赖引入;
  • C 3.0:因 A 1.1 引入的传递约束,需升级至 3.0 以满足版本要求。

约束解析

给定包含版本化依赖项的清单文件后,kmpkg 会尝试计算出满足所有约束的包安装计划。

版本约束分为以下几类:

  • 显式声明的约束:在顶层清单中通过 version>= 显式声明的约束;
  • 基线约束:由 builtin-baseline 隐式添加的约束;
  • 传递约束:由依赖项的依赖(传递依赖)间接引入的约束;
  • 覆盖约束:在顶层清单中通过 overrides 声明覆盖的约束。

kmpkg 计算安装计划的大致流程如下:

  1. 将所有顶层约束加入计划;
  2. 递归将传递约束加入计划:
    • 每次向计划中添加新包时,同时将其基线约束加入计划;
    • 每次添加约束时:
      • 若该包存在覆盖(override):
        • 直接选择覆盖中指定的版本;
      • 若不存在覆盖:
        • 若尚未选择过版本:
          • 选择满足约束的最小版本;
        • 若已选择过版本:
          • 若新约束的版本控制方案与已选版本不一致:
            • 标记版本冲突;
          • 若新约束的版本与已选版本无法比较(例如,将 "version-string: apple" 与 "version-string: orange" 比较):
            • 标记版本冲突;
          • 若新约束的版本高于已选版本:
            • 选择更高的版本;
          • 其他情况:
            • 保留已选版本;
  3. 校验安装计划:
    • 若无冲突:
      • 安装选定的包;
    • 若存在冲突:
      • 向用户报告冲突信息。

获取端口版本

尽管 kmpkg 一直支持包版本的概念,但版本约束是新增能力。

引入版本约束后,可能出现“包依赖的端口版本与本地可用版本不一致”的情况——kmpkg 需要知道如何获取指定版本的端口文件,才能解决该问题。

为此,kmpkg 新增了一套元数据文件,存放在 kmpkg 仓库根目录的 versions/ 目录下。

versions/ 目录中包含注册表内每个端口对应的 JSON 文件,每个文件会列出该包的所有可用版本,并包含一个 Git 树对象(Git tree-ish object),kmpkg 可通过检出该对象获取对应版本的端口文件。

示例:zlib.json

{
"versions": [
{
"git-tree": "2dfc991c739ab9f2605c2ad91a58a7982eb15687",
"version-string": "1.2.11",
"port-version": 9
},
...
{
"git-tree": "a516e5ee220c8250f21821077d0e3dd517f02631",
"version-string": "1.2.10",
"port-version": 0
},
{
"git-tree": "3309ec82cd96d752ff890c441cb20ef49b52bf94",
"version-string": "1.2.8",
"port-version": 0
}
]
}

每个端口的版本文件路径规则为:versions/{端口名称首字母}-/{端口名称}.json。例如,zlib 的版本文件路径为 versions/z-/zlib.json。除端口版本文件外,当前基线文件存放在 versions/baseline.json 中。