包管理器是怎么来的
一句话总结
包管理器不是解压文件的工具,而是求解“什么依赖什么”这张图的工具。
为什么需要这些知识
在使用 tarball 分发软件的年代,安装过程是这样的:解压缩、运行 ./configure、逐个寻找并安装缺失的库;如果那个库又需要其他组件,就继续寻找。这被称为“依赖地狱”。
问题的本质并非移动文件很困难,而是没有人知道应该先移动什么。因此,包格式会给文件集合附加元数据:这个包提供什么(Provides)、依赖什么(Depends)、与什么冲突(Conflicts)。安装从文件复制问题转变为图问题,而计算机比人更擅长求解图问题。
它是如何运作的
deb 与 rpm 的细节不同,骨架却相同。
| 概念 | 含义 | Debian 标记 | RPM 标记 |
|---|---|---|---|
| 名称与版本 | 标识软件包 | Package、Version |
Name、Version、Release |
| 必需依赖 | 缺少就无法使用 | Depends |
Requires |
| 弱依赖 | 有则更好 | Recommends、Suggests |
Recommends、Suggests |
| 提供 | 满足虚拟名称 | Provides |
Provides |
| 冲突 | 无法同时存在 | Conflicts、Breaks |
Conflicts |
| 架构 | 面向哪种 CPU | amd64 / all |
x86_64 / noarch |
这里最常造成阻碍的有三点。
第一,依赖单位不一定是包名称。 RPM 尤其如此。Requires: libnl-3.so.200()(64bit) 这种写法会直接把共享库的 soname作为依赖目标。因此,“只拿一个包回来不就行了吗?”这类要求,在隔离网络中会变成耗费半天的工作。
第二,存在虚拟包(Provides)。 如果依赖 mail-transport-agent,postfix 和 exim4 都能满足要求。因此,会出现“仓库里明明没有这个名称的包,为什么仍然可以安装?”的情况。
第三,版本比较规则并不直观。 Debian 中,1.0~rc1 小于 1.0,因为波浪号小于任何字符。RPM 则有 epoch,所以 1:1.0 大于 2.0。而 epoch 不会出现在文件名中。这是悄无声息却可能致命的差异。
在实际工作中会是什么样
隔离网络中的导入请求经常是“只帮我下载一个 rpm 文件”。拿回来安装后,却出现 nothing provides libnl-3.so.200()(64bit) needed by htop 之类的错误。再次出去下载缺失项,回来后又提示缺少别的组件。USB 往返三四次,半天已经过去,而导入审批每天只有一次。
因此,熟练人员从一开始就会带上整张依赖图的完整求解结果。在哪里完成这项计算,是本课程后半部分的主题。
包管理器不会做的事
包管理器会放置文件并解析依赖,但之后的大部分工作并不负责。不理解这条边界,就会反复遇到“已经安装,为什么仍不能用?”。
不会直接覆盖配置文件。 更新软件包时,如果发现我们修改过配置,管理器通常不会覆盖,而是询问如何处理,或把新文件以 .dpkg-dist、.rpmnew 等名称放在旁边。因此,需要建立更新后检查是否积累了这类文件的流程。新版本需要的配置项可能并不存在于我们的旧文件中,系统会悄然使用默认值运行,直到日后出现问题。
是否重启服务,因发行版而异。 有的发行版更新后会自动重启,有的不会。自动重启可能在意外时刻中断服务;不自动重启则会让旧代码继续运行。正确做法是了解所用发行版的行为,并据此建立流程,而不是盲目信任默认行为。
不会删除并非由它安装的内容。 即使卸载软件包,它产生的日志、数据目录和用户账户通常仍会保留。Debian 系列把 remove 和 purge 分开,正是体现这种差异。保留内容本身是一道安全防线,但如果误以为已经完全删除,日后重新安装同一软件包时,旧配置会再次生效,造成混乱。
不同语言的包管理器彼此并不知情。 系统包安装的 Python 库与通过 pip 安装的库混合后,很难预测实际加载哪一个;系统更新也可能破坏应用程序。因此,应用依赖应通过虚拟环境或容器隔离,并坚持让系统包管理器只管理系统组件。
接下来要做什么
下一步将进入 apt 的实际操作。搜索、安装和删除只是起点,还要掌握使用 hold 和 pin 锁定版本的方法。