0%

办公工具-依赖地狱

一、菱形依赖

所有的依赖问题,大抵都会与“菱形依赖”有关。也就是如图所示, Web LibLog Lib 都依赖于 JSON Lib(相当于菱形的下半部分):

菱形依赖项

Web LibLog Lib 对于 JSON Lib 的依赖要求是不同的,Web Lib 要求 >=1.0 ,而 Log Lib要求 >= 2.0 ,最终的 Log Lib 需要同时满足这两种条件,也就是 >= 2.0

而当这写条件无法满足的时候,这个系统就崩溃了。不过如果软件都保持向后兼容性(backward compability),那么按理说应该我们总能通过“装最新版”的方式来解决依赖问题。但问题就在于,这里是现实:

  • 并不是所有的软件都保证了向后兼容性
  • 并不总能安装特定版本的依赖(硬件不支持,平台已经下架)
  • ……

二、多版本共存

2.1 总论

当我们需要不同版本的依赖的时候,有一个办法通用的办法,就是存储多个版本的依赖,这些依赖分别满足难以兼容的需求们。

但是并不是只要有多个版本的依赖共存,就可以完全解决这个问题了。并没有那么简单。因为此时的软件系统中,就会存在多个版本的依赖,运行和管理都会成为问题。

不同的语言,多版本依赖的支持也是不同的,下面我们会分别展开介绍:

2.2 全局唯一命名

代表的语言就是 C 和 Python。

也就是说,他们是不支持在一个系统中,同时存在两个不同版本的相同依赖的。也就是说,他们一旦遇到菱形依赖冲突的情况,就彻底无法解决了。

C 我不知道,但是 python 的包管理器 pip,在安装某个包的时候,如果系统中存在不满足的依赖的时候,它会直接把依赖卸载掉,装上符合自己要求版本的依赖。这就会导致,可能就安装一个包,就会导致其他包用不了了。

2.3 自己携带自己的依赖

代表的包管理器是 npm

也就是说,当一个包需要某个依赖的时候,它不会安装在全局,或者安装在项目中,而是会安装在自己的 node_modules 下,在运行时,只会依赖自己的库。也就是说,即使两个包依赖相同版本的库,也会在系统中存在两份一模一样的库。

2.4 全局依赖

代表的语言是 Rust 和 Go。

Rust 允许同名不同版本的 crate 链接进同一个 binary。Go 强制要求主版本号升级(v1 到 v2)时,必须修改包的导入路径(例如 github.com/pkg/d 变成 github.com/pkg/d/v2),相当于不同版本的包,就是不同的包。


三、Linux 发行版更新

3.1 总论

整个 GNU/Linux 系统,也可以看作是需要保证依赖没有问题的复杂软件系统。因此也有不同的依赖管理方式。

3.2 固定版本

固定版本(Fixed Release),代表为 Ubuntu。

具有不同的版本,比如说 Ubuntu 22.04 。在特定版本中,所有的软件的版本都是写死的,也就是说,在安装了以后,在 apt upgrade 基本上是没有意义的,并没有办法将某个软件,比如说 emacs 进行升级。

当然这也是有些过于绝对的,基本上我们在一个特定的 Ubuntu 版本中更新,主要是为了更新安全补丁。

3.3 滚动更新

滚动更新(Rolling Release),代表为 Archlinux。

不再有固定的版本,所有的软件都维持最新的。我觉得也挺有意思的,相当于默认假设每个依赖都具有向后兼容性,这才使得将所有依赖都变成最新的,成为可能。

当然事实不会是这样,我们总会因为一些软件,失去向后兼容性,或者恰好不能保持系统完全的最新。导致 ArchLinux 会出现滚挂的局面。

3.4 快照更新

快照更新(Snapshot Release),代表为 NixOS。

我觉得更类似于 project 安装依赖的感觉,类似于 lockfile 的感觉,强调原子更新与可复现性。