LabHub
学习 学习路径 课程

包管理

包管理器是怎么来的

在 LabHub 中继续学习

一句话总结

包管理器不是解压文件的工具,而是求解“什么依赖什么”这张图的工具

概念图: 求解“什么依赖什么”这张图的工具 · 移动文件很困难,而是没有人知道应该先移动什么 · 元数据 · 图问题

为什么需要这些知识

在使用 tarball 分发软件的年代,安装过程是这样的:解压缩、运行 ./configure、逐个寻找并安装缺失的库;如果那个库又需要其他组件,就继续寻找。这被称为“依赖地狱”。

问题的本质并非移动文件很困难,而是没有人知道应该先移动什么。因此,包格式会给文件集合附加元数据:这个包提供什么(Provides)、依赖什么(Depends)、与什么冲突(Conflicts)。安装从文件复制问题转变为图问题,而计算机比人更擅长求解图问题。

它是如何运作的

deb 与 rpm 的细节不同,骨架却相同。

概念 含义 Debian 标记 RPM 标记
名称与版本 标识软件包 PackageVersion NameVersionRelease
必需依赖 缺少就无法使用 Depends Requires
弱依赖 有则更好 RecommendsSuggests RecommendsSuggests
提供 满足虚拟名称 Provides Provides
冲突 无法同时存在 ConflictsBreaks 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 系列把 removepurge 分开,正是体现这种差异。保留内容本身是一道安全防线,但如果误以为已经完全删除,日后重新安装同一软件包时,旧配置会再次生效,造成混乱。

不同语言的包管理器彼此并不知情。 系统包安装的 Python 库与通过 pip 安装的库混合后,很难预测实际加载哪一个;系统更新也可能破坏应用程序。因此,应用依赖应通过虚拟环境或容器隔离,并坚持让系统包管理器只管理系统组件

接下来要做什么

下一步将进入 apt 的实际操作。搜索、安装和删除只是起点,还要掌握使用 hold 和 pin 锁定版本的方法