问题背景

archlinux 是个可高度定制的 linux 发行版,在使用的过程中,需要反复测试很多软件包的功能,以达到自己想要的效果,效果不好的卸载,用得好的留下,但由于依赖是复杂的树状结构,时间长了,容易忘记自己测试过哪些包,以至于有些包只是临时安装的后来忘了卸载,随着积累容易导致在系统里留下大量不必要的包。

pacman 的功能之一是可以查询安装原因,安装原因有两种“单独指定安装”和“作为其他软件包的依赖关系安装”,也可以通过 pacman -Qdt 找出所有没必要的依赖包,pacman -Qe 可以列出所有自己显式指定安装过的包,还可以通过 pacman -Rscn 卸载某个软件来将其不必要的依赖也同时卸载。

虽然 pacman 功能强大,但依然没法满足以上需求,因为很多包都是自己指定安装,但是后来忘了自己只是临时测试这个包,测试过应当卸载,然而这类包显然留在系统中,这些包还不容易被找到,要是数量庞大,基本只能重装系统才能完全干净。

强迫症患者们当然希望自己的系统是干干净净,只有自己需要的包,没有其它任何垃圾包。

于是有了新的脚本需求…

需求设计

需要这样一个脚本,这个脚本能实现:

  1. 能够从一个文本文档里面读取软件列表(不包含“作为其他软件包的依赖关系安装”的包,只需要指定顶层包),根据此列表与系统已安装的包进行对比,进行依赖计算,列出所有多余的包,列出所有指定了却未安装的包。
  2. 软件列表可以有以井号开头的注释,可以忽略空行,每行包含一个软件包。这样可以方便测试注释。
  3. 设计命令行参数,在必要时可以指定不同的配置文件,也可以指定自己喜欢的包管理器命令如 yay ,让脚本自动调用包管理器来同步软件列表和系统。

能满足的需求

要是真的实现了这样一个脚本,用处非常大,我在此罗列几点,充分发挥想象力的话,能带来无尽的乐趣。

  1. **能根据自己的需要完全掌控系统的包:**把自己所有需要的软件做成列表(不需要考虑底层依赖),而且能对每一行的包名后面用井号注释一些自己想写的,让自己一目了然,也让系统里面出了这些包及其依赖的包之外不存在其它任何包,轻而易举地掌控系统的所有包,实现随心所欲高度定制。

  2. **大幅度方便增删测试:**想测试一批软件时,只需要编辑这个软件列表,在里面添加若干行想要测试的软件,然后应用到系统,然后开始随便玩,等当不想用这些软件了,就注释或删掉那些行,再应用到系统,这些软件无影无踪,依赖也一个不留。

    比如我想测试 deepin 的桌面环境,先执行 pacman -Sqg deepin 看看有哪些包,直接把这些包名复制到列表里,然后应用到系统…测完了从列表里删去,再应用到系统,完美回到没测试之前的样子。

  3. **当成系统还原点:**只要我改变列表不变,那么我可以随意安装测试任何软件包,比如装 deepin 组,装任意多的包哪怕装了几十G碎碎的包,玩够了之后,直接应用列表,刚刚装的几十G直接在一分钟之内无影无踪一个不漏,完全回到测试以前的样子,此方法就是将一个列表看作是一个还原点,甚至可以设置多个还原点(多个列表)进行任意测试,配合 pacman 的装卸极速特性,基本可以随便玩了。

项目实现

实现思路

选择用 python来实现,因为 python的列表和字典非常好用,先从系统中读取所有软件包的信息,放到一个巨大的列表里,然后将每个软件包名作为字典的键,构建出一个大字典,然后对依赖进行整合,然后同样对列表里所有包进行这样处理,算出一个所需的包的集合,将系统里所有包也弄成一个集合,将两个集合直接相减,也就算出了所有多余的包了。

以下难点一个一个被攻破:

  1. 难点: 读取所有软件包信息,转换成 python 列表。

    解决: 模拟执行 LANG=C pacman -Qi ,然后字符串处理。

  2. 难点: 某些包的依赖(Depends On)是一些包的提供字段(Provides)。

    **解决:**利用字典的索引特性,把每个 Depends On 的内容转换成具体包名。

  3. 难点: 某些包的依赖(Depends On)是版本号的对比,比如 java-runtime>=8,而版本由好几个段组成,比较算法可能过于复杂。

    **解决:**查询 libalpm 的开发文档得知,里面有个 C 库函数 alpm_pkg_vercmp 被封装在 libalpm.so 中,直接模拟调用,版本比较问题解决。

本项目已经用 python3 实现,我将它取名为超级包管理器,脚本名称为 spacman ,放在 github 上开源托管。方便以后直接调用,已经自己打包上传到了 aur,可以用 yay 直接安装。

1
yay -S spacman

所有强迫症患者的福音!

用法

命令语法

1
2
3
4
5
6
7
8
9
10
用法: spacman [-h] [--config 列表文件] [--pacman 包管理器] [--apply] [--query]

可选参数:
-h, --help 显示帮助信息
--config 列表文件, -c 列表文件
指定列表文件(默认为 ~/.config/spacman/default.conf)
--pacman 包管理器, -p 包管理器
指定包管理器(例如 yay,默认为 pacman)
--apply, -a 自动调用包管理器,将列表应用到系统
--query, -q 查询一个列表中所有的包

以上用法可能已经过时没有更新,详见 spacman --help

用法举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 将 ~/.config/spacman/default.conf 列表与系统已安装的包进行对比,输出结果
spacman

# 将 ~/spacman1.conf 作为列表进行对比,输出结果
spacman -c ~/spacman1.conf

# 将 ~/.config/spacman/default.conf 列表应用到系统
spacman -a
# 警告,万万不可将空列表应用到系统,否则会卸载所有软件包

# 将 ~/.config/spacman/default.conf 列表应用到系统,并用 yay 作为包管理器
spacman -a -p yay

# 列出 ~/.config/spacman/default.conf 列表中所有软件包并排序
spacman -q | sort

如何写配置

到底该如何写配置文件呢,首先要自己总结出自己所有需要的包列表,写到一个文本文档里,只需要写你需要的包,不需要操心任何依赖,一行一个,格式如下:

1
2
3
4
5
6
7
8
9
linux
linux-firmware
base
grub
dhcpcd
iw
wpa_supplicant
archlinuxcn-keyring
yay

当然为了你的方便,你可以把配置文件当成笔记,顺便记录linux软件包名和功能,井号开头注释即可,也可以在包名后面跟井号注释,空行随意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 内核
linux
linux-firmware # 固件

# 基本
base

# 引导器
grub

# 网络
dhcpcd # dhcp客户端
iw # 无线管理
wpa_supplicant # 无线加密

#
archlinuxcn-keyring

# aur helper
yay

如果嫌麻烦,可以用 pacman -Qe 快速生成一个列表,这命令表示列出所有自己用pacman主动安装的包名。

1
pacman -Qe > a.conf

但是不建议这样做,因为生成的列表是按字母排序,而且自己整理注释起来麻烦,还不如自己从头写个。

我的日常列表也托管到 github 了,可以随时参考 spacman.conf

使用逻辑

我暂时想出以下使用方法,大家可以尽情的发挥想象发挥更多的潜力。

  1. 当成个人做的笔记记录,用linux用久了自己也记不清自己需要哪些包,配置文件可以刚好帮你记录。
  2. 配置列表中,自己想删去哪个包了,可以井号注释掉,而不必删掉一行,然后 spacman -a 即可,以后想反悔直接去掉井号注释,再次 spacman -a
  3. 可以把所有自己可能需要的同类软件包都记录下来,然后都用井号注释掉,然后想用哪个直接去掉哪个的井号。
  4. 实验各个软件,而不加进列表,只把满意的软件加进列表,不满意的由于没加进列表,直接 spacman -a 会删掉所有没进列表的软件包括其依赖,而不必一个一个 pacman -Rsc 卸载,省心又高效。例如实验各个桌面环境,一个gnome桌面环境有多少个顶层包咱们也知道。