这个问题是这个问题的后续问题:如何安装 apt-get 不可用的软件包版本?
在链接的问题中,我了解了 Linux 软件包“ecosystemm”,包括不同的 Linux 发行版如何使用不同的软件包格式,有些实际上让用户直接从源代码编译+安装新软件包!
作为后续行动,我想问/学习为什么封装格式存在这种差异。
所链接问题的公认答案是:
[Linux] 的所有子分支主要由正在使用的包管理系统定义。
我想了解的一些内容是:
为什么软件包格式如此重要以至于它们推动了整个 Linux 发行版的创建?
为什么首先要有不同的包格式?我天真地想象包包含可执行文件、共享/静态库、标头等的某种组合,即一些“文件集合”,它们通常进入由文件系统层次结构标准——难道不是一种包格式能够普遍解决该问题和/或随着时间的推移证明自己是最优越的吗?即,每个发行版的使用.deb
vs. .rpm
vs..zip
等的基本原理是什么?
为什么有时相同的包格式会有不同的包管理器?例如,根据我的理解,Debian 和 Ubuntu 都使用.deb
文件作为其软件包,但 Debian 使用文件dpkg
作为其软件包管理器,而 Ubuntu 使用apt
(和apt-get
?!)。
答案1
包管理是一个非常复杂的问题,并且已经有多种尝试来解决它。每一次尝试都试图改进前一次,但没有一个是完美的。
每个包管理系统都使用自己的格式,因为包管理系统和格式是一起设计的。理论上可以有一种所有系统都可以使用的统一格式,但是包管理的其他方面,例如依赖图生成、补丁管理、配置文件内容和放置,甚至包之间的版本兼容性也必须改变。统一以使包可以互换。
由于每个包管理系统都采用不同的方法来进行依赖关系管理,并且每个发行版都采用不同的方法来进行包版本选择和补丁管理,因此无法统一这些内容。
这些选择构成了 Linux 发行版。试图统一它们就是尝试将所有发行版合并到一个发行版中。这就是为什么包管理系统是发行版的核心——这就是决定发行版的决策所在。确实没有比这更好的方法来解释这一点了https://xkcd.com/927/
人们已经多次尝试制作可移植的包格式。 Appimage 就是其中之一,它的工作原理是将(几乎)所有依赖项嵌入到分发包中,这使得它们非常大并且使用了不必要的内存。
另一种选择是 snap,它再次将依赖项与操作系统分离并尝试自行处理它们,以便一个 snap 包可以引入它所依赖的其他 snap 包,再次复制版本不同的操作系统组件。
最终的可移植基础包是必须手动修补、配置、编译和安装的源代码文件的 tar 球。这不包含任何依赖项,因此安装程序也需要在编译之前解决依赖项。情况变得更糟,因为没有列出依赖项的标准方法,并且有些软件包甚至不麻烦,因为对于系统上哪些东西可以作为标准使用有很多假设,一直到 libc 中的内容。
只是没有更好的方法来解决这个问题。每个解决方案都有问题。创建解决方案来解决问题只会产生不同的问题,而且许多解决方案是相互排斥的。
答案2
简单的答案是主要原因因为 Linux 发行版的存在是为了,嗯,分发一组连贯的包。由于您不想永远陷入相同的版本,因此您还需要一种升级方法。由于您不想安装不需要的软件,因此您需要一种方法来选择所需的软件包。由于您无法预测未来,因此稍后您将需要一种方法来更改此选择,即安装或删除软件包。由于重用是软件工程的基本原则之一,包依赖于其他包,因此您需要一种方法来处理这些依赖关系。
所有这些导致了包管理系统的创建。
因此,如果有人创建了一个新的 Linux 发行版,这可能意味着他们认为每个现有的 Linux 发行版都存在问题。由于包管理对于 Linux 发行版来说是如此基础,因此他们不同意的事情之一很可能就是包管理系统,这是理所当然的。
这并没有真正回答“为什么我们有多个包管理系统”的问题,而是将其推回到“为什么我们有多个 Linux 发行版”。
现在轮到你不问题为什么有多个Linux发行版。但是,您也不应该质疑为什么有多个包管理器。
顺便说一句,这里没有任何特定于 Linux 的内容。大多数 BSD 也有自己的包管理器。商业 Unices 有包管理器。
哎呀,它甚至不是 Unix 特有的。仅 Windows 就有六种不同的软件安装方式,其中一些可以说是包管理器:Microsoft 创建了 NuGet 包管理器,但它仅适用于 .NET,因此社区创建了 Chocolatey 包管理器,然后 Microsoft 创建了 WinGet 包管理器,还有允许您安装和删除应用程序的 Microsoft Store,还有 Microsoft Installer,……此外,仅就 Microsoft Installer 而言,就有大约十几种不同的工具来创建 MSI 包,而不仅仅是来自第三方,但即使是微软本身也有多种工具。
但还有另一个原因:包管理,尤其是依赖管理是一个极其困难问题要解决,但它看起来看似简单(这个问题就是一个很好的例子)。
事实上,这个问题极其难以解决,这意味着几乎每个人都有出现问题的第一手经验。事实上,它看起来看似简单,这意味着几乎每个有过第一手出错经验的人都会认为“这很愚蠢,我可以做得更好”。
和,巴姆,现在你有了N+1个包管理系统。
答案3
汇编
让我们从这里开始,因为所有发行版都在“后台”共享这一点。每个软件在被选为发行版的一部分之前都会在某个时刻进行编译。正如您正确推测的那样,一个完整的编译程序可以包含以下一项或全部内容:
- 库:它们通常存储为共享对象文件或存档。看.a 和 .so 文件有什么区别?
- 一个头文件。看GCC 文档中的头文件
- 二进制文件,又称可执行程序
编译到打包
同样,正如您所指出的,每个发行版都有软件包,并且每个发行版都有一个软件包管理器。我认为可能会引起混乱的地方是:包装格式和包装内容不是包装管理系统。任何发行版的包管理系统都包含 3 个不同的部分:
- 包格式,即编译部分中的项目如何存储或排列
- 打包工具 - 根据 #1 打包已编译项目的可执行文件/二进制文件。这将创建包。
- 包管理器 - 重复调用 #2 中的工具来操作包的可执行文件/二进制文件。操纵是指行动,其中可以包括:
- 安装/重新安装
- 移动
- 更新配置文件
- 更新所有已安装的软件包
为什么要创建包管理器
在包管理器出现之前,使用打包工具安装包是完全可以接受的。以这种方式安装软件包时出现的问题是:
- 我在 Debian 上安装并启动
foo.deb
包dpkg -i foo.deb
- 我尝试使用以下命令运行 foo.deb
foo
- 程序突然关闭并显示错误消息:程序 foo 需要 bar.so;未找到文件,正在退出
- 好吧,现在我陷入困境,我真的需要执行
foo
,所以我重复步骤 1 - 3bar.deb
- 我重试并收到另一个错误程序bar需要静态库baz.a然后程序退出。
打包到包管理器
现在我已经走了六步并且非常生气,因为我真的很想使用foo
.如果有一个程序可以为我完成这一切该有多好。这是包管理器的主要工作。包管理器在内存中绘制一个依赖关系图,计算 需要foo
的部分bar
和bar
需要 的部分baz
。它不断计算,向后工作,直到找到没有依赖项的包,然后向前查看是否已安装任何所需的包。如果已安装该包并启用了所需的配置,则会将其从图表中标记出来并搜索下一个包。如果搜索到的下一个包需要更新以满足要求,则会查询包存储库并添加更新的版本。哇哦,现在我们必须重新开始图表,或者重新绘制一个新的分支,因为我们刚刚添加的更新也有要求。
每次安装或更新软件包时,都会一遍又一遍地重复此过程。
命名差异
从技术上讲,包管理器之间没有区别。每个管理器都在其安装的系统上出色地执行了前面讨论的功能。看到您提到的 FH 标准在满足要求后非常灵活,每个发行版都决定调整一些与包相关的东西,他们在包装管理器中这样做。在这个概念提出的时候,主要是为了品牌知名度。其中的 R.rpm
代表 Red Hat,3 个字母的.deb
扩展名是 Debian 的缩写。
为什么编译自己的包是危险的
在您链接的问题中,您正在寻找更新的libuv
,但该版本在 Ubuntu Repo 中不可用jammy
。由于不可用,编译新版本是唯一的选择,尽管它不是最好的选择:
- 编译软件时,包管理系统无法管理您编译的内容,因为包管理器在您编译后从未安装该包。简而言之,除了托管非托管软件之外,您现在还面临着管理非托管软件的更多麻烦,并且您失去了包管理器提供的功能,尽管是针对 1 个包。
- 确保编译后的包已存储里面客户网站的。根据上面的观点,不使用非托管软件污染主机操作系统始终符合主机操作系统的最佳利益。
- 这意味着对所述程序的任何调用都是本地的。
/bin
当您想提及客户时,不要错误地提及主机/bin