6.3. 软件包管理

软件包管理是我们经常收到希望添加到 LFS 手册里的需求。一个软件包管理器可以追踪安装的文件,这样可以在移除或升级软件包时轻松地清理。不仅是二进制执行文件和库文件,包管理器还会处理配置文件的安装。在你遐想之前提醒一下,不!——本节不涉及也不建议任何一个特定的包管理器,而是关于软件包管理的更一般的技术的综合概述。最适合你的包管理器可能就在这些技术里,或者可能是两个或更多的组合。本节还会简要提到升级软件包可能碰到的问题。

关于为什么 LFS 或 BLFS 手册里不采用任何软件包管理器的一些原因:

关于软件包管理有很多资料,请访问 Hints Project 看看是否有一个能适合你的需求。

6.3.1. 升级问题

软件包管理器可以在软件新版本发布后轻松升级。一般来说 LFS 和 BLFS 手册里的指令可以用来升级到新版本。下面是一些在你准备升级软件包时需要注意的事情,特别是在一个运行中的系统。

  • 如果需要升级 Glibc 到新版本(比如,从 glibc-2.19 升级到 glibc-2.20),重新构建整个 LFS 会比较安全。虽然你也许能够按依赖关系重新编译所有的软件包,不过我们不建议这样做。

  • 如果某个包含的动态库的软件包升级了,而且库名字有改变,那么所有动态链接到这个库的软件包都需要重新链接新的库。(请注意软件包版本和库名字并不存在相关性。)举个例子,某个软件包 foo-1.2.3 安装了一个名叫 libfoo.so.1 的动态库。然后假设你把这个软件包升级到了新版本 foo-1.2.4,而新版本会安装名叫 libfoo.so.2的动态库。在这种情况下,所有动态链接到 libfoo.so.1 的软件包都需要重新编译链接到 libfoo.so.2。注意在所有依赖软件包重新编译完成之前,请不要删除旧版的库文件。

6.3.2. 软件包管理技术

下面介绍一些常见的软件包管理技术。在决定用哪种包管理方式之前,先研究一下各种不同的技术,特别是了解特定体系下的不足。

6.3.2.1. 所有一切都在我脑袋里!

是的,这也算一种软件包管理技术。有些人觉得不需要管理软件包,是因为他们非常熟悉软件包,知道每个包都安装了哪些文件。也有些用户不需要管理软件包,是因为他们会在某个软件包有更改后重建整个系统。

6.3.2.2. 在独立目录里安装

这是一种简单的软件包管理方式,不需要其他额外的软件来管理软件的安装。每一个软件包都被装到一个独立的目录里。例如,软件包 foo-1.1 安装到目录 /usr/pkg/foo-1.1 里并创建一个软链接 /usr/pkg/foo 指向 /usr/pkg/foo-1.1。在安装新版本 foo-1.2 的时候,它会被装到目录 /usr/pkg/foo-1.2 里,然后将之前的软链接替换成指向新版本软件的位置。

类似 PATHLD_LIBRARY_PATHMANPATHINFOPATHCPPFLAGS 之类的环境变量需要包含 /usr/pkg/foo 目录。在管理大量软件包时,这种方式就不可行了。

6.3.2.3. 软链接方式软件包管理

这是前一种软件包管理技术的变种。每个软件包都和之前方式一样的安装。但不是建立目录的软链接,而是把每个文件都链接到 /usr 目录结构里。这样就不需要扩展环境变量了。通过自动创建这些可由用户自己创建的链接,许多软件包管理器都采用了这种方式。几个比较流行的有 Stow、Epkg、Graft 和 Depot。

这种安装方式需要伪装,这样软件包会认为自己被装到了 /usr 目录下,而实际上它被装到了 /usr/pkg 目录结构中。在这种方式下,安装并不是一件琐碎的小事。例如,假如你准备安装一个软件包 libfoo-1.1。下面的指令可能不会正确地安装:

./configure --prefix=/usr/pkg/libfoo/1.1
make
make install

安装本身倒是没有问题,但是可能一些依赖包不会像你期望的那样链接 libfoo 库。如果要编译一个链接 libfoo 的软件,你可能会注意到它实际上链接的是 /usr/pkg/libfoo/1.1/lib/libfoo.so.1 而不是你所期望的 /usr/lib/libfoo.so.1。正确的方式是使用 DESTDIR 策略来伪装软件包的安装过程。这种方式需要像下面这样操作:

./configure --prefix=/usr
make
make DESTDIR=/usr/pkg/libfoo/1.1 install

大多数软件包支持这种方式,但也有一些例外。对于不兼容的软件包,你可能需要自己手动安装,或许你会发现将这些有问题的包安装到 /opt 目录下会更简单些。

6.3.2.4. 基于时间戳

在这种方式里,在安装之前会创建一个时间戳文件。在安装之后,用一行简单的 find 命令加上合适的参数就可以生成在时间戳文件创建之后所安装的所有文件列表。有一个采用这种方式的包管理器叫做 install-log。

这种方式的优点是非常简单,但是它有两个缺陷。比如,在安装过程中,所安装文件采用的是其它时间戳而不是当前时间,那这些文件将不能被软件包管理器跟踪到。还有,这种方式只能在一次安装一个软件包的情况下使用。如果在不同的终端里同时安装两个不同的软件包,此时的安装日志就不可靠了。

6.3.2.5. 追踪安装脚本

在这种方式里,安装脚本所使用的命令都会被记录下来。有两种技术,一种技术是:

设定环境变量 LD_PRELOAD 指向一个在安装前预加载的库。在安装过程中,这个库会追踪软件包安装脚本里所包含的各种执行文件比如 cpinstallmv,以及追踪会修改文件系统的系统调用。要让这种方式有效的话,所有的执行文件需要动态链接到没有 suid 或 sgid 标志位的库。预加载这个库可能会引起安装过程中一些意外的副作用。不过,建议做一些测试以保证软件包管理器不会造成破坏并且记录了所有适当的文件。

第二种技术是使用 strace 命令,它会记录下安装脚本执行过程中所有的系统调用。

6.3.2.6. 创建软件包存档

在这种方式里,像之前的软链接软件包管理方式里所描述的那样,软件包被伪装安装到一个独立的目录树里。在安装完成后,会将已安装文件打包成一个软件包存档。然后这个存档会用来在本地机器或其他机器上安装软件包。

这种方式为商业发行版中的大多数包管理器所采用。一些例子是 RPM(它顺便也是 Linux 标准规范 里所要求的)、pkg-utils、Debian 的 apt、以及 Gentoo 的 Portage 系统。该页面描述了如何在 LFS 系统里采用这种包管理方式: http://www.linuxfromscratch.org/hints/downloads/files/fakeroot.txt

创建带有依赖关系的软件包存档非常复杂,已经超出 LFS 手册范围了。

Slackware 使用一个基于 tar 的系统来创建软件包存档。这套系统不像那些更复杂的包管理器,有意地不处理包依赖关系。关于 Slackware 包管理器的详细信息,请参看 http://www.slackbook.org/html/package-management.html

6.3.2.7. 基于用户的软件包管理

在这种方式,是 LFS 特有的,由 Matthias Benkmann 所设计,可以在 Hints Project 里能找到。在这种方式里,每个软件包都由一个单独的用户安装到标准的位置。属于某个软件包的文件可以通过检查用户 ID 轻松识别出来。关于这种方式的特性和短处非常复杂,在本节里说不清楚。详细的信息请参看 http://www.linuxfromscratch.org/hints/downloads/files/more_control_and_pkg_man.txt

6.3.3. 在多个系统上布置 LFS

LFS 系统的一个优点是没有会依赖磁盘系统里文件位置的文件。克隆一份 LFS 到和宿主机器相似配置的机器上,简单到只要对包含根目录的 LFS 分区(对于一个基本的 LFS 构建不压缩的话大概有 250MB)使用 tar命令打包,然后通过网络传输或光盘拷贝到新机器上展开即可。在这之后,还需要调整一些配置文件,包括:/etc/hosts/etc/fstab/etc/passwd/etc/group/etc/shadow/etc/ld.so.conf

根据系统硬件和原始内核配置文件的差异,可能还需要重新编译一下内核。

最后,需要使用 8.4 “用 GRUB 设置引导过程”里所介绍的方法让新系统可引导。