Last active 1747638210

Compare two manifest files and list packages present in the first but not the second.

RUN.sh Raw
1# 保存脚本为 compare_manifests.py,并赋可执行权限
2chmod +x compare_manifests.py
3
4# 运行脚本,依次传入 Ubuntu、Zorin、Anduin 三个 manifest 文件路径
5./compare_manifests.py \
6 "/media/anduin/Ubuntu 25.04 amd64/casper/filesystem.manifest" \
7 "/media/anduin/Zorin OS 17.3 Core 64bit/casper/filesystem.manifest" \
8 "/media/anduin/anduinos/casper/filesystem.manifest"
9
compare_manifest.sh Raw
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4compare_manifests.py
5
6读取三个 filesystem.manifest 文件,比较包的并集,并输出包含发行版勾选标记、Priority、Section、Description 的 Markdown 表格。
7增加详细日志输出,并使用多线程加速 apt show 调用。
8修复:自动剥离包名中的 ":<arch>" 后缀。
9"""
10import sys
11import subprocess
12import logging
13import concurrent.futures
14import time
15
16# 日志配置
17logging.basicConfig(
18 level=logging.INFO,
19 format="%(asctime)s [%(levelname)s] %(message)s",
20 datefmt="%Y-%m-%d %H:%M:%S"
21)
22
23
24def parse_manifest(path):
25 """
26 读取 manifest 文件,返回其中所有包名的集合。
27 自动剥离包名中的 ":<arch>" 后缀。
28 """
29 pkgs = set()
30 logging.info(f"Parsing manifest: {path}")
31 try:
32 with open(path, encoding='utf-8') as f:
33 for line in f:
34 line = line.strip()
35 if not line or line.startswith('#'):
36 continue
37 raw_pkg = line.split()[0]
38 pkg = raw_pkg.split(':', 1)[0]
39 pkgs.add(pkg)
40 logging.info(f"Found {len(pkgs)} unique packages in {path}")
41 except Exception as e:
42 logging.error(f"Error reading manifest {path}: {e}")
43 sys.exit(1)
44 return pkgs
45
46
47def get_pkg_info(pkg):
48 """
49 调用 `apt show pkg`,解析 Priority、Section、Description(首段),
50 返回 dict 包含信息。
51 若调用失败,返回空字段。
52 """
53 logging.debug(f"Fetching apt info for package: {pkg}")
54 try:
55 p = subprocess.run(
56 ["apt", "show", pkg],
57 stdout=subprocess.PIPE,
58 stderr=subprocess.DEVNULL,
59 text=True,
60 encoding='utf-8',
61 timeout=30
62 )
63 except subprocess.TimeoutExpired:
64 logging.warning(f"Timeout fetching info for {pkg}")
65 return {"Priority": "", "Section": "", "Description": ""}
66
67 if p.returncode != 0:
68 logging.debug(f"apt show failed for {pkg}")
69 return {"Priority": "", "Section": "", "Description": ""}
70
71 priority = ""
72 section = ""
73 desc_lines = []
74 in_desc = False
75
76 for line in p.stdout.splitlines():
77 if in_desc:
78 if not line.strip():
79 break
80 desc_lines.append(line.strip())
81 else:
82 if line.startswith("Priority:"):
83 priority = line.split(":", 1)[1].strip()
84 elif line.startswith("Section:"):
85 section = line.split(":", 1)[1].strip()
86 elif line.startswith("Description:"):
87 desc = line.split(":", 1)[1].strip()
88 desc_lines.append(desc)
89 in_desc = True
90
91 full_desc = " ".join(desc_lines)
92 logging.debug(f"Obtained info for {pkg}: priority={priority}, section={section}")
93 return {"Priority": priority, "Section": section, "Description": full_desc}
94
95
96def main():
97 if len(sys.argv) != 4:
98 print(f"Usage: {sys.argv[0]} <ubuntu_manifest> <zorin_manifest> <anduin_manifest>")
99 sys.exit(1)
100
101 start_time = time.time()
102 paths = {
103 "Ubuntu": sys.argv[1],
104 "Zorin": sys.argv[2],
105 "Anduin": sys.argv[3],
106 }
107
108 # 解析 manifests
109 sets = {name: parse_manifest(path) for name, path in paths.items()}
110 all_pkgs = sorted(set.union(*sets.values()))
111 total = len(all_pkgs)
112 logging.info(f"Total unique packages to process: {total}")
113
114 # 多线程获取 apt 信息
115 pkg_infos = {}
116 max_workers = min(10, total)
117 logging.info(f"Starting ThreadPoolExecutor with {max_workers} workers")
118
119 with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
120 future_to_pkg = {executor.submit(get_pkg_info, pkg): pkg for pkg in all_pkgs}
121 for idx, future in enumerate(concurrent.futures.as_completed(future_to_pkg), 1):
122 pkg = future_to_pkg[future]
123 try:
124 pkg_infos[pkg] = future.result()
125 logging.info(f"[{idx}/{total}] Processed {pkg}")
126 except Exception as e:
127 logging.error(f"Error processing {pkg}: {e}")
128 pkg_infos[pkg] = {"Priority": "", "Section": "", "Description": ""}
129
130 # 写入 Markdown
131 out_path = "comp_result.md"
132 logging.info(f"Writing results to {out_path}")
133 with open(out_path, "w", encoding="utf-8") as out:
134 # 新表头:检查标记 -> Priority -> Section -> Description
135 out.write("| Package | Ubuntu | Zorin | Anduin | Priority | Section | Description |\n")
136 out.write("|---------|--------|-------|--------|----------|---------|-------------|\n")
137
138 for pkg in all_pkgs:
139 info = pkg_infos.get(pkg, {"Priority": "", "Section": "", "Description": ""})
140 mark = lambda name: "√" if pkg in sets[name] else ""
141 desc = info["Description"].replace("|", "\\|")
142 out.write(
143 f"| {pkg} | {mark('Ubuntu')} | {mark('Zorin')} | {mark('Anduin')} |"
144 f" {info['Priority']} | {info['Section']} | {desc} |\n"
145 )
146
147 elapsed = time.time() - start_time
148 logging.info(f"Completed in {elapsed:.2f}s")
149
150if __name__ == "__main__":
151 main()
152