Debian 软件包不仅是一个待安装文件的归档。它是一个更大结构的一部分,它描述与其它 Debian 软件包的关系(前置、依赖、冲突、建议)。它还提供执行的脚本,供管理套件在软件包生命周期的不同阶段(安装、升级、删除)执行。软件包中的这些数据供包管理工具使用,但不是软件包的一部分,称为“元信息”——关于软件包的信息。
本文件使用的结构与email头相似(如由
RFC 2822 定义的),并且在 Debian 政策和手册页面
deb-control(5) 和
deb822(5) 中充分描述。
例如,对于 apt 软件包,control
文件看起来像这样:
$
apt-cache show apt
Package: apt
Version: 2.2.4
Installed-Size: 4337
Maintainer: APT Development Team <deity@lists.debian.org>
Architecture: amd64
Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~)
Provides: apt-transport-https (= 2.2.4)
Depends: adduser, gpgv | gpgv2 | gpgv1, libapt-pkg6.0 (>= 2.2.4), debian-archive-keyring, libc6 (>= 2.15), libgcc-s1 (>= 3.0), libgnutls30 (>= 3.7.0), libseccomp2 (>= 2.4.2), libstdc++6 (>= 9), libsystemd0
Recommends: ca-certificates
Suggests: apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2), gnupg | gnupg2 | gnupg1, powermgmt-base
Breaks: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~), aptitude (<< 0.8.10)
Description-en: commandline package manager
This package provides commandline tools for searching and
managing as well as querying information about packages
as a low-level access to all features of the libapt-pkg library.
.
These include:
* apt-get for retrieval of packages and information about them
from authenticated sources and for installation, upgrade and
removal of packages together with their dependencies
* apt-cache for querying available information about installed
as well as installable packages
* apt-cdrom to use removable media as a source for packages
* apt-config as an interface to the configuration settings
* apt-key as an interface to manage authentication keys
Description-md5: 9fb97a88cb7383934ef963352b53b4a7
Tag: admin::package-management, devel::lang:ruby, hardware::storage,
hardware::storage:cd, implemented-in::c++, implemented-in::perl,
implemented-in::ruby, interface::commandline, network::client,
protocol::ftp, protocol::http, protocol::ipv6, role::program,
scope::application, scope::utility, suite::debian, use::downloading,
use::organizing, use::playing, use::searching, works-with-format::html,
works-with::audio, works-with::software:package, works-with::text
Section: admin
Priority: required
Filename: pool/main/a/apt/apt_2.2.4_amd64.deb
Size: 1491328
MD5sum: 24d53e8dd75095640a167f40476c0442
SHA256: 75f07c4965ff0813f26623a1164e162538f5e94defba6961347527ed71bc4f3d
我们来看看上一条命令列出来的部分字段的含义。
依赖关系在软件包标头的 Depends
字段中定义,它是软件包能够正常运行所需要满足的条件的列表。这个信息由诸如 apt
这样的工具来使用,来安装所需的适当版本的库、工具、驱动等等,来满足安装的软件包的依赖。对于每一项依赖,可以限定满足条件的版本范围。换句话说,可以这样表达:我们需要版本号等于或者大于“2.15”的 libc6 软件包(写作 “libc6 (>= 2.15)
”)。版本比较操作符使用如下:
条件的列表,以逗号作为交集 “and” 符号。以直线 (“|”) 为联集 “or” 符号 (它是包容性的或 “or”、不是排它性的非此即彼 “either/or”)。优先级比 “and” 高,可以无限次使用。因此,依赖 “(A or B) and C” 可写成
A | B, C
。另一个例子,“A or (B and C)” 应写成 “(A or B) and (A or C)”,因为
Depends
字段不接受以括号改变逻辑操作数 “or” 与 “and”的优先次序。所以须写成
A | B, A | C
。
依赖系统是保证程序顺利运作的良好机制,但它也有另个用法 「元软件包」。这些空的软件软件包内容只有相依性的说明。由元软件包维护者把一群程序的相依性描述在其中;例如,apt install meta-package
将自动安装所有用到元软件包相依的文件。gnome、kde-full 与 linux-image-amd64 软件包就是元软件包之一。
Conflicts
字段表示一个软件包不能与另一个软件包同时安装。最常见的原因是这两个软件包都包含相同名称和路径的文件,或在相同的 TCP 端口上提供相同的服务,或会妨碍对方的运行。
dpkg
会拒绝安装与已安装软件包冲突的软件包,除非新软件包指定要“替换”已安装的软件包,在这种情况下,dpkg
会选择用新软件包替换旧软件包。apt
总是遵循你的指示:如果你选择安装一个新软件包,它会自动卸载有问题的软件包。
Breaks
字段的作用与 Conflicts
字段类似,但有不同含义。它表示安装这个软件包会“破坏”另一个软件包(或其特定版本)。一般来说,两个软件包之间的这种不兼容性是暂时的,Breaks
关系会指出不兼容的版本。
dpkg
会拒绝安装会破坏已安装软件包的软件包,而 apt
则会尝试通过将被破坏的软件包更新到新的版本(假定新版本已被修复,因此重新变为兼容)来解决问题。
这种情况可能发生在没有向后兼容性的更新中:如果新版本与旧版本的功能有差异,那么在没有指明的情况下,就会导致相关的另一个程序出现故障。Breaks
字段可防止用户遇到这些问题。
这一字段引入了 "虚包 "这一非常有趣的概念。它有许多作用,但有两个作用尤为重要。第一个作用是使用虚拟软件包来关联通用服务(软件包“提供”服务)。第二个作用是,表明一个软件包可以完全替代另一个软件包,为此,它也可以满足另一个软件包所要满足的依赖关系。因此,我们可以创建一个替代包,而不必使用相同的包名。
让我们举例详细讨论第一种情况:所有邮件服务器,如 postfix 或 sendmail 都“提供”了 mail-transport-agent 虚包。因此,任何需要这种服务才能运行的软件包(例如邮件列表管理器,如 smartlist 或 sympa)只需在其依赖关系中说明它需要 mail-transport-agent 而不是指定一个庞大但不完整的可能解决方案列表(如 postfix | sendmail | exim4 | ...
)。此外,在同一台机器上安装两个邮件服务器是没有用的,这就是为什么每个软件包都声明与mail-transport-agent虚拟软件包冲突。系统会忽略软件包与自身的冲突,但这种声明技巧将禁止并行安装两个邮件服务器。
当一个软件包的内容包含在一个更大的软件包中时,Provides
字段也很有趣。例如,libdigest-md5-perl Perl 模块在 Perl 5.6 中是一个可选模块,在 Perl 5.8(以及更高的版本,例如 Bullseye中的 5.32.1)中已作为标准模块集成。因此,perl包从 5.8 版开始就声明了 提供:libdigest-md5-perl
,这样如果用户使用 Perl 5.8(或更新版本),就可以满足对该包的依赖。libdigest-md5-perl软件包本身最终已被删除,因为当旧的 Perl 版本被移除后,它就不再有任何作用了。
这一功能非常有用,因为我们永远不可能预料到变化莫测的发展,因此有必要对过时软件的重命名和其他自动替换进行调整。
虚拟软件包曾经受到一些限制,其中最明显的是没有版本号。回到前面的例子,一个依赖关系如 Depends:libdigest-md5-perl (>= 1.6)
,尽管存在 Perl 5.10,但打包系统绝不会认为它已满足要求,而事实上它很可能已满足要求。由于没有意识到这一点,打包系统选择了风险最小的选项,假定版本不匹配。
这一限制已在 dpkg 1.17.11 中解除,不再适用。像 perl 5.32.1 这样的软件包可以为其提供的虚拟软件包指定一个版本,例如 提供:libdigest-md5-perl (= 2.55.01)
,从而允许其他软件包使用版本化的依赖关系。
Replaces
字段表示软件包包含的文件也存在于另一个软件包中,但该软件包可以对它们合法替换。如果没有这一说明,dpkg
将无法安装该软件包,并指出它不能覆盖另一个软件包的文件(从技术上讲,可以使用 --force-overwrite
选项来强制执行,但这不是标准操作)。这样可以发现潜在的问题,并要求维护者在选择是否添加这样一个字段之前对问题进行研究。
当软件包名称改变或一个软件包包含在另一个软件包中时,就需要使用这个字段。当维护者决定在从同一源码包生成的不同二进制包中以不同方式分发文件时,也会出现这种情况:被替换的文件不再属于旧包,而只属于新包。
如果已安装软件包的所有文件都被替换了,那么该软件包将认为将被删除掉。最后,这个字段会鼓励dpkg
命令去删除替换掉有冲突的包。
除了
control
文件外,每个 Debian 软件包的
control.tar.gz
压缩包可能包含许多脚本,在处理软件包的不同阶段被
dpkg
调用。Debian 策略详细描述了可能出现的情况,指明了调用的脚本及其接收的参数。这些序列可能比较复杂,因为如果其中一个脚本失败,
dpkg
将尝试通过取消正在进行的安装或移除(在可能的情况下)来返回到令人满意的状态。
在一般情况下,preinst
脚本会在安装软件包前执行,而postinst
会稍后执行。同样,prerm
会在移除一个软件包的之前被调用,postrm
则随后执行。更新软件包相当于是清除掉以前的旧版本并且安装新的软件包。所以在这里我们不可能来详细描述所有的可能的方案,但我们将讨论最常见的两种:安装/更新和移除。
在软件包的初始安装和其后续的每次升级时,dpkg
都会调用所谓的维护者脚本,例如prerm
或者preinst
脚本。在软件包生命周期的不同阶段,这些脚本可以执行一些额外的操作。前缀为new-
的脚本来自软件包正在安装或升级到的那个新版本。前缀为old-
的脚本来自软件包正在被升级的那个旧版本。
每次调用中,dpkg
都会向每个脚本传递参数,例如 upgrade new-version
。然后,被调用的脚本可以处理这些参数并执行特定操作;或者,如果在该步骤中没有需要执行的操作,就忽略这些参数并以退出代码0
返回。实践中,许多软件包在生命周期的每个步骤中都不需要执行操作。因此,典型的配置脚本检查特定的单个参数并忽略所有其他参数,隐式地以退出代码0
返回。
以下是安装(或更新)时的情况。 old-version、new-version和last-version-configured参数是实际软件包(旧版和新版)的版本号的占位符:
对于升级,dpkg
会调用 old-prerm
脚本,并传递 upgrade new-version
参数。
还是对于升级,dpkg
会执行 new-preinst
脚本,参数为 upgrade old-version
;而在初始安装时,它会执行 new-preinst
脚本,并传递 install
参数。如果软件包已被安装并移除(但未被清除,因此配置文件被保留),它可能会在最后一个参数中添加旧版本。
然后解压新的软件包文件。如果文件已经存在,则会被替换,但会临时制作一份备份。
对于升级,dpkg
执行 old-postrm
脚本,并传递 upgrade new-version
参数。
dpkg
更新所有的内部数据(文件列表,配置脚本等),并删除被替换文件的备份。这是一条不归路: dpkg
将不能够再访问回退到之前状态所需要的所有元素。
最后,dpkg
通过执行 new-postinst
脚本,并传递 configure last-version-configured
参数,对软件包进行配置。
卸载软件包的步骤和安装类似。主要区别在于这里调用的是软件包的卸载脚本:
dpkg
调用 prerm
脚本,并传递 remove
参数。
dpkg
移除了所有的软件包文件,只剩下了配置文件和维护者脚本。
dpkg
执行 postrm
脚本,并传递 remove
参数。随后,除 postrm
脚本外的所有维护者脚本都会被删除。如果用户没有使用“清除(purge)”选项,则进程到此为止。
对于完全清除软件包(使用 dpkg --purge
或 dpkg -P
发出的命令),配置文件以及一定数量的副本(*.dpkg-tmp
、*.dpkg-old
、*.dpkg-new
)和临时文件都会被删除;然后 dpkg
执行 postrm
脚本,并传递 purge
参数。
config
脚本补充前述的 4 个脚本,软件包以 debconf
取得配置用的信息。安装过程中,此脚本以 debconf
指令询问用户详细的问题。把回应记录在 debconf
数据库供未来的参考。在安装之前先由 apt
逐一运行该等脚本,归纳问题与回答。事前与事后安装脚本可使用该等信息回应用户的期望。
除了前面提到的维护者脚本和控制数据之外,Debian软件包的control.tar.gz
压缩包可能包含其他有趣的文件。
conffiles
文件列出了必须作为配置文件处理的软件包文件(还请参见 deb-conffiles(5))。管理员可修改配置文件,当升级软件包时 dpkg
将尝试保留这些更改。
实际上,在这种情况下,dpkg
的行为会尽可能地智能:如果两个版本之间并没有改变标准的配置文件,那么它就什么也不会做。但是如果文件已经被修改,它会尝试更新此文件。这会产生两种可能的情况:一是管理员没有碰配置文件,在这种情况下 dpkg
会自动安装新的版本;二是文件已经被修改了,在这种情况下 dpkg
会询问管理员希望使用哪个版本(有过修改的旧版本,或者软件包的新版本)。为了帮助用户作出决定,dpkg
会提供显示 “diff
”,这会显示两个版本之间的差异。如果用户选择旧版本,那么新版本将被存储在文件夹的同一个位置并以 .dpkg-dist
为后缀名的文件中。如果用户选择新版本,那么旧版本将被保留在以 .dpkg-old
为后缀名的文件中。另外一个可能的操作是由暂时中断 dpkg
来编辑该文件,并试图重新恢复相关的修改(之前用 diff
来识别)。
控制压缩包通常也包含其他文件,例如triggers
、shlibs
或symbols
。这些文件在deb-triggers(5)、deb-shlibs(5)和deb-symbols(5)中有很好的描述。
shlibs
系统可以代替共享库声明依赖项的symbols
系统,它更旧、更简单。它定义软件包名称和版本,其可用于查找共享库的特定SONAME版本。而新的symbols
系统定义依赖项的方式则是跟踪库中引入或更改符号的时间。