软件包依赖关系分析工具
ssk-wh Lv4

原理

通过 apt 命令分析系统中安装的软件包的依赖关系,生成符合 mermaid 语法的配置文件,再通过 dot 命令生成 svg 图。

实现

确保电脑上安装了 dot 命令
sudo apt install graphviz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#! /bin/bash

# Author: ssk-wh
# Date: 2024.01.29

# @para: null
# 输出帮助信息
function print_usage() {
echo "Usage:
用于分析软件包依赖的工具,最终在当前目录生成一张依赖关系图

-h 输出帮助手册
-a 输出所有已安装软件包的依赖关系 (不要用 -a 参数,生成的 svg 图恐怕没有电脑能流畅地查看)
-m : 对指定软件包进行mark,生成的svg图中会进行特殊标记
-d : 指定查找深度,默认不限制,仅当指定分析特定应用时生效,配合 -p 参数生效
-p : 分析指定软件包的被依赖关系图

用例:
分析dde-dock的被依赖关系图,递归查找深度为2,如果存在startdde,就加个颜色标记
generate_rdepends.sh -p dde-dock -d 2 -m startdde

分析所有已安装软件包的依赖关系
generate_rdepends.sh -a"
}

rdepends_file="/tmp/rdepends.tmp"

# @para:file 目标文件
# @para: text 传入的内容
# 将内容以追加的方式写入文件中
# @example: write_to_file "/tmp/tmp.file" "write strings"
function write_to_file() {
local _dest_file=$1
local _write_string=$2
echo $_write_string >> $_dest_file
}

# @para:null
# 获取已安装软件包列表,返回一个数组
function get_installed_packages() {
local _packages=() # 定义空数组存储软件包名称

# 使用 dpkg -l 命令获取已安装软件包列表,提取软件包名称并添加到数组中(排除带dbgsym或者dev的包)
mapfile -t _packages < <(dpkg -l | grep -vi "dbgsym"| awk '/^ii/ {print $2}')

# 返回软件包名称数组
printf '%s\n' "${_packages[@]}"
}


# @para: package 软件包名
# 分析此软件包被哪些软件包所依赖,返回一组包名列表(仅统计系统中安装的应用)
# @example: get_package_rdepends "dde-dock"
function get_package_rdepends() {
local _package=$1

local _limit_packages=($(get_installed_packages))
# echo "系统中安装的应用列表:"
# printf '%s\n' "${_limit_packages[@]}"

command_output=$(apt rdepends $_package)

output_list=()
while IFS= read -r line; do
# echo "line:" $line
# 分析包含"依赖"的行
if [[ "$line" == *"依赖: "* ]]; then
output_string="${line/依赖: /}"
# 去掉竖线
output_string=$(echo "$output_string" | tr -d '|')
# 去掉版本信息
while [[ $output_string == *'('* && $output_string == *')'* ]]; do
output_string="${output_string%%(*}${output_string#*)}"
done
fi

# 去除空格
output_string=$(echo "$output_string" | sed 's/ //g')
if ! [[ " ${_limit_packages[*]} " == *"$output_string"* ]]; then
# "未安装的软件包,忽略"
# echo "@@@"
continue;
fi

# 将结果添加到列表中(如果列表中不存在相同的内容)
if ! [[ " ${output_list[@]} " =~ " $output_string " ]]; then
output_list+=("$output_string")
# echo "###" $output_string
fi

done <<< "$command_output"

echo ${output_list[@]}
}

# 查过的软件包都记录下,避免重复查询
used_package_list=()
# 记录当前查询深度
depth=1

# @para: package 软件包名
# @para: package_rdepends_list 包名列表
# 递归函数:分析此软件包和package_rdepends_list中的包名间的被依赖关系,层层查找,将内容输出到配置文件中
# @example: generate_package_digraph "dde-dock" $package_list
function generate_package_digraph() {
local _package=$1
local _depth=$2
local _package_mark=$3
shift
shift
shift # 将参数向左移动一个位置以获得数组
local _package_rdepends_list=("$@") # 获取移动后的参数列表

depth=$((depth + 1))
if [ "$_depth" -ne -1 ] && [ "$depth" -gt $_depth ]; then
echo "^^^ 超出最大查询深度: $_depth"
else
# 已经进行过rdenpeds操作的包记录一下,避免重复统计
if ! [[ " ${used_package_list[@]} " =~ " $_package " ]]; then
used_package_list+=("$_package")

# 获取数组的大小
array_size="${#_package_rdepends_list[@]}"
# 判断数组是否为空并进行相应操作
if [[ $array_size -ne 0 ]]; then
echo "== 正在生成软件包的依赖数据:" $_package "被依赖软件包列表数量:" $array_size
# echo " depth:" $depth
# 遍历列表并输出每个字符串
for rdepends_package in "${_package_rdepends_list[@]}"; do
echo " handle package:" $rdepends_package
if [[ "$rdepends_package" == "$_package_mark" ]]; then
write_to_file $rdepends_file " node [shape=box,style=filled,color=\".7 .3 1.0\"];"
fi
write_to_file $rdepends_file " \"$_package\"->\"$rdepends_package\";"
if [[ "$rdepends_package" == "$_package_mark" ]]; then
write_to_file $rdepends_file " node [shape=\"\", style=\"\", color=\"\"];"
fi
done

# 递归一遍
for rdepends_package in "${_package_rdepends_list[@]}"; do
new_package_rdepends_list=($(get_package_rdepends "$rdepends_package"))
generate_package_digraph "$rdepends_package" "$_depth" "$_package_mark" ${new_package_rdepends_list[@]}
done

fi
else
echo "已查询过,忽略本次查询"
fi
fi
depth=$((depth - 1))
}


# 生成所有安装应用的的依赖关系图
function generate_all() {
local _package_mark=$1

# 1\清除旧的配置文件
rm -f $rdepends_file
touch $rdepends_file

# 2\生成head部分内容
write_to_file $rdepends_file "digraph basicGraph {"
write_to_file $rdepends_file "rankdir = LR "

# 3\逐个遍历已安装的应用
_installed_packages=($(get_installed_packages))
for package in "${_installed_packages[@]}"; do
echo "handle package:" $package
_rdepends_list=($(get_package_rdepends $package))
for rdepends_package in "${_rdepends_list[@]}"; do
write_to_file $rdepends_file " \"$package\"->\"$rdepends_package\";"
done
done

# 4\生成foot部分内容
write_to_file $rdepends_file "}"

# 5\将配置文件转换为svg图片
dot -Tsvg $rdepends_file -o rdepends.svg

echo "done"
}

# 生成指定包的关系图
function generate_by_package() {
local _package=$1
local _depth=$2
local _package_mark=$3

# 1\清除旧的配置文件
rm -f $rdepends_file
touch $rdepends_file

# 2\生成head部分内容
write_to_file $rdepends_file "digraph basicGraph {"
write_to_file $rdepends_file "rankdir = LR "

echo "开始查找软件包的被依赖关系:" $_package
# package_rdepends_list=($(get_package_rdepends "$_package" "$_installed_packages"))
# printf '%s\n' "${package_rdepends_list[@]}"
# exit 0

# 3\开始查询
# 查找被哪些包依赖,在递归这些被依赖的包又被哪些包依赖,递归一次层深加1,可以通过 -d 参数指定查询深度
# bash的第一个小坑,取list要加括号
package_rdepends_list=($(get_package_rdepends "$_package"))
# bash的第二个小坑,不要传入一个空的参数,会其他后面的参数受到影响
generate_package_digraph "$_package" "$_depth" "$_package_mark" "${package_rdepends_list[@]}"

# 4\生成foot部分内容
write_to_file $rdepends_file "}"

# 5\将配置文件转换为svg图片
dot -Tsvg $rdepends_file -o $_package".svg"

echo "done"
}


################################################ start ################################################

option_mark="dde"
option_depth=-1
option_package=""

option_mark_set=false
option_all_set=false

# 解析命令行参数
while getopts ":am:d:p:h" opt; do
case $opt in
a)
option_all_set=true
;;
m)
option_mark="$OPTARG"
option_mark_set=true
;;
d)
option_depth="$OPTARG"
;;
p)
option_package="$OPTARG"
;;
h)
print_usage
exit 0
;;
\?)
echo "Invalid option: -$OPTARG"
exit 1
;;
:)
echo "Option -$OPTARG requires an argument."
exit 1
;;
esac
done

# -a 则分析所有已安装软件包的依赖关系
if $option_all_set; then
# 默认mark dde项目
generate_all $option_mark
exit 0
fi

# 指定的软件包名不为空,分析此软件包的被依赖关系
if [ -n "$option_package" ]; then
if ! $option_mark_set; then
# 指定 -p 但未指定 -m,直接标记 -p 指向的软件包
option_mark=$option_package
fi

echo "package:" $option_package
echo "depth:" $option_depth
echo "mark:" $option_mark
generate_by_package $option_package $option_depth $option_mark
exit 0
else
print_usage
exit 0
fi

用法

可以给上述脚本添加可执行权限,运行时添加 -h 参数可输出帮助信息。
chmod +x generate_rdepends.sh
generate_rdepends.sh -h

实战

在 UOS 系统中运行以下命令
generate_rdepends.sh -p libdtkwidget5 -m dde-dock
得到的依赖图内容如下

image

 Comments