anduin revised this gist . Go to revision
1 file changed, 111 insertions, 5 deletions
visualize.py
@@ -5,6 +5,108 @@ from concurrent.futures import ThreadPoolExecutor | |||
5 | 5 | import threading | |
6 | 6 | import os | |
7 | 7 | import sys | |
8 | + | import webbrowser | |
9 | + | import urllib.request | |
10 | + | ||
11 | + | MERMAID_TEMPLATE = """ | |
12 | + | <!DOCTYPE html> | |
13 | + | <html> | |
14 | + | <head> | |
15 | + | <meta charset="UTF-8"> | |
16 | + | <script src="mermaid.min.js"></script> | |
17 | + | <script src="panzoom.min.js"></script> | |
18 | + | <script> | |
19 | + | document.addEventListener("DOMContentLoaded", () => {{ | |
20 | + | mermaid.initialize({{ startOnLoad: true }}); | |
21 | + | ||
22 | + | mermaid.contentLoaded().then(() => {{ | |
23 | + | const svg = document.querySelector(".mermaid > svg"); | |
24 | + | if (svg) {{ | |
25 | + | const panzoom = Panzoom(svg, {{ maxScale: 5, minScale: 0.5 }}); | |
26 | + | svg.parentElement.addEventListener('wheel', panzoom.zoomWithWheel); | |
27 | + | }} else {{ | |
28 | + | console.error("Mermaid SVG not found for Panzoom initialization."); | |
29 | + | }} | |
30 | + | }}); | |
31 | + | }}); | |
32 | + | </script> | |
33 | + | <style> | |
34 | + | body {{ | |
35 | + | margin: 0; | |
36 | + | overflow: hidden; | |
37 | + | display: flex; | |
38 | + | justify-content: center; | |
39 | + | align-items: center; | |
40 | + | height: 100vh; | |
41 | + | background-color: #f4f4f4; | |
42 | + | }} | |
43 | + | #diagram-container {{ | |
44 | + | width: 100%; | |
45 | + | height: 100%; | |
46 | + | overflow: hidden; | |
47 | + | position: relative; | |
48 | + | }} | |
49 | + | .mermaid {{ | |
50 | + | width: 100%; | |
51 | + | height: 100%; | |
52 | + | }} | |
53 | + | </style> | |
54 | + | </head> | |
55 | + | <body> | |
56 | + | <div id="diagram-container"> | |
57 | + | <div class="mermaid"> | |
58 | + | {graph_content} | |
59 | + | </div> | |
60 | + | </div> | |
61 | + | </body> | |
62 | + | </html> | |
63 | + | """ | |
64 | + | ||
65 | + | ||
66 | + | def ensure_local_files(): | |
67 | + | """Ensure Mermaid.js and Panzoom.js are available locally.""" | |
68 | + | js_files = { | |
69 | + | "mermaid.min.js": "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js", | |
70 | + | "panzoom.min.js": "https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js", | |
71 | + | } | |
72 | + | tmp_dir = "/tmp" | |
73 | + | ||
74 | + | for filename, url in js_files.items(): | |
75 | + | file_path = os.path.join(tmp_dir, filename) | |
76 | + | if not os.path.exists(file_path): | |
77 | + | print(f"Downloading {filename}...") | |
78 | + | urllib.request.urlretrieve(url, file_path) | |
79 | + | ||
80 | + | def preview_mermaid_graph(file_path): | |
81 | + | """Preview Mermaid graph in a browser.""" | |
82 | + | ensure_local_files() | |
83 | + | ||
84 | + | if not os.path.exists(file_path): | |
85 | + | print(f"File {file_path} does not exist.") | |
86 | + | return | |
87 | + | ||
88 | + | # Load the Mermaid graph content | |
89 | + | with open(file_path, "r") as f: | |
90 | + | graph_content = f.read() | |
91 | + | ||
92 | + | # Prepare the HTML file content | |
93 | + | html_content = MERMAID_TEMPLATE.format(graph_content=graph_content) | |
94 | + | ||
95 | + | # Write the HTML to a temporary file | |
96 | + | html_file_path = f"{file_path}.html" | |
97 | + | with open(html_file_path, "w") as html_file: | |
98 | + | html_file.write(html_content) | |
99 | + | ||
100 | + | # Copy JS files to the same directory | |
101 | + | for js_file in ["mermaid.min.js", "panzoom.min.js"]: | |
102 | + | src = os.path.join("/tmp", js_file) | |
103 | + | dst = os.path.join(os.path.dirname(html_file_path), js_file) | |
104 | + | if not os.path.exists(dst): | |
105 | + | subprocess.run(["cp", src, dst]) | |
106 | + | ||
107 | + | # Open the HTML file in the default web browser | |
108 | + | print(f"Opening {html_file_path} in the browser...") | |
109 | + | webbrowser.open(f"file://{os.path.abspath(html_file_path)}", new=2) | |
8 | 110 | ||
9 | 111 | def list_installed_packages(): | |
10 | 112 | """Retrieve a list of all installed packages.""" | |
@@ -34,7 +136,7 @@ def build_dependency_graph(packages): | |||
34 | 136 | """Build a dependency graph for the packages and save it to a file.""" | |
35 | 137 | graph = defaultdict(list) | |
36 | 138 | lock = threading.Lock() | |
37 | - | ||
139 | + | ||
38 | 140 | def process_package(package): | |
39 | 141 | dependencies = get_package_dependencies(package) | |
40 | 142 | with lock: | |
@@ -115,13 +217,15 @@ def depends_mode(package, exclude_leaves): | |||
115 | 217 | print("Generating dependency graph...") | |
116 | 218 | mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves) | |
117 | 219 | ||
118 | - | with open(f"{package}_depends.mmd", "w") as f: | |
220 | + | output_file = f"{package}_depends.mmd" | |
221 | + | with open(output_file, "w") as f: | |
119 | 222 | f.write("---\n") | |
120 | 223 | f.write(f"title: {package} Dependency Graph\n") | |
121 | 224 | f.write("---\n\n") | |
122 | 225 | f.write(mermaid_graph) | |
123 | 226 | ||
124 | - | print(f"Dependency graph generated and saved as {package}_depends.mmd") | |
227 | + | print(f"Dependency graph generated and saved as {output_file}") | |
228 | + | preview_mermaid_graph(output_file) | |
125 | 229 | ||
126 | 230 | def rdepends_mode(package, exclude_leaves): | |
127 | 231 | _, reverse_graph = load_dependency_graph() | |
@@ -132,13 +236,15 @@ def rdepends_mode(package, exclude_leaves): | |||
132 | 236 | print("Generating reverse dependency graph...") | |
133 | 237 | mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves) | |
134 | 238 | ||
135 | - | with open(f"{package}_rdepends.mmd", "w") as f: | |
239 | + | output_file = f"{package}_rdepends.mmd" | |
240 | + | with open(output_file, "w") as f: | |
136 | 241 | f.write("---\n") | |
137 | 242 | f.write(f"title: {package} Reverse Dependency Graph\n") | |
138 | 243 | f.write("---\n\n") | |
139 | 244 | f.write(mermaid_graph) | |
140 | 245 | ||
141 | - | print(f"Reverse dependency graph generated and saved as {package}_rdepends.mmd") | |
246 | + | print(f"Reverse dependency graph generated and saved as {output_file}") | |
247 | + | preview_mermaid_graph(output_file) | |
142 | 248 | ||
143 | 249 | def main(): | |
144 | 250 | if len(sys.argv) < 2: |
anduin revised this gist . Go to revision
1 file changed, 129 insertions, 58 deletions
visualize.py
@@ -1,8 +1,13 @@ | |||
1 | + | #!/usr/bin/env python3 | |
1 | 2 | import subprocess | |
2 | - | from collections import defaultdict | |
3 | + | from collections import defaultdict, deque | |
4 | + | from concurrent.futures import ThreadPoolExecutor | |
5 | + | import threading | |
6 | + | import os | |
7 | + | import sys | |
3 | 8 | ||
4 | 9 | def list_installed_packages(): | |
5 | - | """获取所有已安装的包列表。""" | |
10 | + | """Retrieve a list of all installed packages.""" | |
6 | 11 | result = subprocess.run( | |
7 | 12 | ["dpkg-query", "-f", "${binary:Package}\n", "-W"], | |
8 | 13 | stdout=subprocess.PIPE, | |
@@ -11,7 +16,7 @@ def list_installed_packages(): | |||
11 | 16 | return result.stdout.strip().split("\n") | |
12 | 17 | ||
13 | 18 | def get_package_dependencies(package): | |
14 | - | """查询单个包的直接依赖项。""" | |
19 | + | """Query the direct dependencies of a single package.""" | |
15 | 20 | result = subprocess.run( | |
16 | 21 | ["apt-cache", "depends", package], | |
17 | 22 | stdout=subprocess.PIPE, | |
@@ -21,77 +26,143 @@ def get_package_dependencies(package): | |||
21 | 26 | for line in result.stdout.strip().split("\n"): | |
22 | 27 | if line.strip().startswith("Depends:"): | |
23 | 28 | dep = line.split(":", 1)[1].strip() | |
24 | - | dep = dep.split(":")[0] # 去掉冒号后的部分 | |
29 | + | dep = dep.split(":")[0] # Remove parts after colon | |
25 | 30 | dependencies.append(dep) | |
26 | 31 | return dependencies | |
27 | 32 | ||
28 | 33 | def build_dependency_graph(packages): | |
29 | - | """构建包的依赖关系图。""" | |
30 | - | totalPackages = len(packages) | |
31 | - | processedPackages = 0 | |
34 | + | """Build a dependency graph for the packages and save it to a file.""" | |
32 | 35 | graph = defaultdict(list) | |
33 | - | for package in packages: | |
36 | + | lock = threading.Lock() | |
37 | + | ||
38 | + | def process_package(package): | |
34 | 39 | dependencies = get_package_dependencies(package) | |
35 | - | for dep in dependencies: | |
36 | - | print(f"{package} -> {dep}") | |
37 | - | graph[package].append(dep) | |
38 | - | processedPackages += 1 | |
39 | - | print(f"已处理 {processedPackages}/{totalPackages} 个包") | |
40 | - | return graph | |
41 | - | ||
42 | - | def remove_redundant_edges(graph): | |
43 | - | """去除冗余的边。""" | |
44 | - | def dfs(node, visited): | |
45 | - | if node in visited: | |
46 | - | return visited[node] | |
47 | - | visited[node] = set() | |
48 | - | for neighbor in graph[node]: | |
49 | - | visited[node].update(dfs(neighbor, visited)) | |
50 | - | visited[node].add(node) | |
51 | - | return visited[node] | |
52 | - | ||
53 | - | # 创建 graph 的静态副本来避免动态修改引发问题 | |
54 | - | nodes = list(graph.keys()) | |
55 | - | reachable = {} | |
56 | - | for node in nodes: # 这里使用静态副本 | |
57 | - | dfs(node, reachable) | |
58 | - | ||
59 | - | minimal_graph = defaultdict(list) | |
60 | - | for node in nodes: # 再次使用静态副本 | |
61 | - | direct_deps = set(graph[node]) | |
62 | - | for dep in graph[node]: | |
63 | - | direct_deps -= reachable[dep] | |
64 | - | minimal_graph[node] = list(direct_deps) | |
65 | - | return minimal_graph | |
66 | - | ||
67 | - | def generate_mermaid_graph(graph): | |
68 | - | """生成 Mermaid 图表的语法。""" | |
40 | + | with lock: | |
41 | + | graph[package].extend(dependencies) | |
42 | + | ||
43 | + | total_packages = len(packages) | |
44 | + | with ThreadPoolExecutor(max_workers=20) as executor: | |
45 | + | for i, _ in enumerate(executor.map(process_package, packages), start=1): | |
46 | + | progress = (i / total_packages) * 100 | |
47 | + | print(f"Building dependency graph... {progress:.2f}% completed", end="\r") | |
48 | + | ||
49 | + | output_path = "/tmp/pkg.txt" | |
50 | + | with open(output_path, "w") as f: | |
51 | + | for package, dependencies in graph.items(): | |
52 | + | for dep in dependencies: | |
53 | + | f.write(f"{package}-->{dep}\n") | |
54 | + | ||
55 | + | print(f"\nDependency graph built and saved to {output_path}") | |
56 | + | ||
57 | + | def load_dependency_graph(file_path="/tmp/pkg.txt"): | |
58 | + | """Load the dependency graph from a file.""" | |
59 | + | if not os.path.exists(file_path): | |
60 | + | raise FileNotFoundError(f"File {file_path} does not exist. Please run the build mode first.") | |
61 | + | ||
62 | + | graph = defaultdict(list) | |
63 | + | reverse_graph = defaultdict(list) | |
64 | + | ||
65 | + | with open(file_path, "r") as f: | |
66 | + | for line in f: | |
67 | + | line = line.strip() | |
68 | + | if "-->" in line: | |
69 | + | source, target = line.split("-->") | |
70 | + | graph[source].append(target) | |
71 | + | reverse_graph[target].append(source) | |
72 | + | ||
73 | + | return graph, reverse_graph | |
74 | + | ||
75 | + | def trim_package_name(package): | |
76 | + | """Trim package name to conform to Mermaid syntax.""" | |
77 | + | return package.replace("-", "_").replace(".", "_").replace("+", "_").replace(":", "_").replace("<", "_").replace(">", "_") | |
78 | + | ||
79 | + | def generate_mermaid_graph(graph, root_package, exclude_leaves=False): | |
80 | + | """Generate a Mermaid diagram syntax for the graph.""" | |
69 | 81 | lines = ["stateDiagram-v2"] | |
70 | - | for package, dependencies in graph.items(): | |
82 | + | visited = set() | |
83 | + | queue = deque([root_package]) | |
84 | + | is_leaf = lambda pkg: len(graph.get(pkg, [])) == 0 # Determine if it is a leaf node | |
85 | + | ||
86 | + | while queue: | |
87 | + | package = queue.popleft() | |
88 | + | if package in visited: | |
89 | + | continue | |
90 | + | visited.add(package) | |
91 | + | ||
92 | + | dependencies = graph.get(package, []) | |
71 | 93 | for dep in dependencies: | |
72 | - | lines.append(f" {package} --> {dep}") | |
94 | + | if exclude_leaves and is_leaf(dep): | |
95 | + | continue # Skip leaf nodes | |
96 | + | ||
97 | + | lines.append(f" {trim_package_name(package)} --> {trim_package_name(dep)}") | |
98 | + | if dep not in visited: | |
99 | + | queue.append(dep) | |
100 | + | ||
73 | 101 | return "\n".join(lines) | |
74 | 102 | ||
75 | - | def main(): | |
76 | - | print("正在获取已安装的包...") | |
103 | + | def build_mode(): | |
104 | + | print("Retrieving installed packages...") | |
77 | 105 | packages = list_installed_packages() | |
106 | + | print("Building dependency graph...") | |
107 | + | build_dependency_graph(packages) | |
108 | + | ||
109 | + | def depends_mode(package, exclude_leaves): | |
110 | + | graph, _ = load_dependency_graph() | |
111 | + | if package not in graph: | |
112 | + | print(f"Package {package} is not in the dependency graph.") | |
113 | + | return | |
114 | + | ||
115 | + | print("Generating dependency graph...") | |
116 | + | mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves) | |
78 | 117 | ||
79 | - | print("正在构建依赖图...") | |
80 | - | graph = build_dependency_graph(packages) | |
118 | + | with open(f"{package}_depends.mmd", "w") as f: | |
119 | + | f.write("---\n") | |
120 | + | f.write(f"title: {package} Dependency Graph\n") | |
121 | + | f.write("---\n\n") | |
122 | + | f.write(mermaid_graph) | |
81 | 123 | ||
82 | - | print("正在去除冗余边...") | |
83 | - | minimal_graph = remove_redundant_edges(graph) | |
124 | + | print(f"Dependency graph generated and saved as {package}_depends.mmd") | |
84 | 125 | ||
85 | - | print("正在生成 Mermaid 图表语法...") | |
86 | - | mermaid_graph = generate_mermaid_graph(minimal_graph) | |
126 | + | def rdepends_mode(package, exclude_leaves): | |
127 | + | _, reverse_graph = load_dependency_graph() | |
128 | + | if package not in reverse_graph: | |
129 | + | print(f"Package {package} is not in the reverse dependency graph.") | |
130 | + | return | |
131 | + | ||
132 | + | print("Generating reverse dependency graph...") | |
133 | + | mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves) | |
134 | + | ||
135 | + | with open(f"{package}_rdepends.mmd", "w") as f: | |
136 | + | f.write("---\n") | |
137 | + | f.write(f"title: {package} Reverse Dependency Graph\n") | |
138 | + | f.write("---\n\n") | |
139 | + | f.write(mermaid_graph) | |
140 | + | ||
141 | + | print(f"Reverse dependency graph generated and saved as {package}_rdepends.mmd") | |
142 | + | ||
143 | + | def main(): | |
144 | + | if len(sys.argv) < 2: | |
145 | + | print("Usage: ./vispkg.py [build|depends|rdepends] [package] [--no-leaves]") | |
146 | + | sys.exit(1) | |
87 | 147 | ||
88 | - | with open("dependency_graph.mmd", "w") as file: | |
89 | - | file.write("---\n") | |
90 | - | file.write("title: APT Dependency Graph\n") | |
91 | - | file.write("---\n\n") | |
92 | - | file.write(mermaid_graph) | |
148 | + | mode = sys.argv[1] | |
149 | + | exclude_leaves = "--no-leaves" in sys.argv | |
93 | 150 | ||
94 | - | print("Mermaid 图表已生成并保存为 dependency_graph.mmd") | |
151 | + | if mode == "build": | |
152 | + | build_mode() | |
153 | + | elif mode == "depends": | |
154 | + | if len(sys.argv) < 3: | |
155 | + | print("Usage: ./vispkg.py depends <package> [--no-leaves]") | |
156 | + | sys.exit(1) | |
157 | + | depends_mode(sys.argv[2], exclude_leaves) | |
158 | + | elif mode == "rdepends": | |
159 | + | if len(sys.argv) < 3: | |
160 | + | print("Usage: ./vispkg.py rdepends <package> [--no-leaves]") | |
161 | + | sys.exit(1) | |
162 | + | rdepends_mode(sys.argv[2], exclude_leaves) | |
163 | + | else: | |
164 | + | print("Unknown mode. Please use: build, depends, or rdepends.") | |
165 | + | sys.exit(1) | |
95 | 166 | ||
96 | 167 | if __name__ == "__main__": | |
97 | 168 | main() |
anduin revised this gist . Go to revision
1 file changed, 97 insertions
visualize.py(file created)
@@ -0,0 +1,97 @@ | |||
1 | + | import subprocess | |
2 | + | from collections import defaultdict | |
3 | + | ||
4 | + | def list_installed_packages(): | |
5 | + | """获取所有已安装的包列表。""" | |
6 | + | result = subprocess.run( | |
7 | + | ["dpkg-query", "-f", "${binary:Package}\n", "-W"], | |
8 | + | stdout=subprocess.PIPE, | |
9 | + | text=True | |
10 | + | ) | |
11 | + | return result.stdout.strip().split("\n") | |
12 | + | ||
13 | + | def get_package_dependencies(package): | |
14 | + | """查询单个包的直接依赖项。""" | |
15 | + | result = subprocess.run( | |
16 | + | ["apt-cache", "depends", package], | |
17 | + | stdout=subprocess.PIPE, | |
18 | + | text=True | |
19 | + | ) | |
20 | + | dependencies = [] | |
21 | + | for line in result.stdout.strip().split("\n"): | |
22 | + | if line.strip().startswith("Depends:"): | |
23 | + | dep = line.split(":", 1)[1].strip() | |
24 | + | dep = dep.split(":")[0] # 去掉冒号后的部分 | |
25 | + | dependencies.append(dep) | |
26 | + | return dependencies | |
27 | + | ||
28 | + | def build_dependency_graph(packages): | |
29 | + | """构建包的依赖关系图。""" | |
30 | + | totalPackages = len(packages) | |
31 | + | processedPackages = 0 | |
32 | + | graph = defaultdict(list) | |
33 | + | for package in packages: | |
34 | + | dependencies = get_package_dependencies(package) | |
35 | + | for dep in dependencies: | |
36 | + | print(f"{package} -> {dep}") | |
37 | + | graph[package].append(dep) | |
38 | + | processedPackages += 1 | |
39 | + | print(f"已处理 {processedPackages}/{totalPackages} 个包") | |
40 | + | return graph | |
41 | + | ||
42 | + | def remove_redundant_edges(graph): | |
43 | + | """去除冗余的边。""" | |
44 | + | def dfs(node, visited): | |
45 | + | if node in visited: | |
46 | + | return visited[node] | |
47 | + | visited[node] = set() | |
48 | + | for neighbor in graph[node]: | |
49 | + | visited[node].update(dfs(neighbor, visited)) | |
50 | + | visited[node].add(node) | |
51 | + | return visited[node] | |
52 | + | ||
53 | + | # 创建 graph 的静态副本来避免动态修改引发问题 | |
54 | + | nodes = list(graph.keys()) | |
55 | + | reachable = {} | |
56 | + | for node in nodes: # 这里使用静态副本 | |
57 | + | dfs(node, reachable) | |
58 | + | ||
59 | + | minimal_graph = defaultdict(list) | |
60 | + | for node in nodes: # 再次使用静态副本 | |
61 | + | direct_deps = set(graph[node]) | |
62 | + | for dep in graph[node]: | |
63 | + | direct_deps -= reachable[dep] | |
64 | + | minimal_graph[node] = list(direct_deps) | |
65 | + | return minimal_graph | |
66 | + | ||
67 | + | def generate_mermaid_graph(graph): | |
68 | + | """生成 Mermaid 图表的语法。""" | |
69 | + | lines = ["stateDiagram-v2"] | |
70 | + | for package, dependencies in graph.items(): | |
71 | + | for dep in dependencies: | |
72 | + | lines.append(f" {package} --> {dep}") | |
73 | + | return "\n".join(lines) | |
74 | + | ||
75 | + | def main(): | |
76 | + | print("正在获取已安装的包...") | |
77 | + | packages = list_installed_packages() | |
78 | + | ||
79 | + | print("正在构建依赖图...") | |
80 | + | graph = build_dependency_graph(packages) | |
81 | + | ||
82 | + | print("正在去除冗余边...") | |
83 | + | minimal_graph = remove_redundant_edges(graph) | |
84 | + | ||
85 | + | print("正在生成 Mermaid 图表语法...") | |
86 | + | mermaid_graph = generate_mermaid_graph(minimal_graph) | |
87 | + | ||
88 | + | with open("dependency_graph.mmd", "w") as file: | |
89 | + | file.write("---\n") | |
90 | + | file.write("title: APT Dependency Graph\n") | |
91 | + | file.write("---\n\n") | |
92 | + | file.write(mermaid_graph) | |
93 | + | ||
94 | + | print("Mermaid 图表已生成并保存为 dependency_graph.mmd") | |
95 | + | ||
96 | + | if __name__ == "__main__": | |
97 | + | main() |
anduin revised this gist . Go to revision
2 files changed, 26 insertions
compare.sh(file created)
@@ -0,0 +1,24 @@ | |||
1 | + | #!/usr/bin/env bash | |
2 | + | # | |
3 | + | # compare_packages.sh | |
4 | + | # | |
5 | + | # Compare package lists from two systems: | |
6 | + | # anduinos-packages.txt | |
7 | + | # ubuntu-24-packages.txt | |
8 | + | ||
9 | + | # Ensure both files exist | |
10 | + | if [[ ! -f "anduinos-packages.txt" || ! -f "ubuntu-24-packages.txt" ]]; then | |
11 | + | echo "Error: One or both package list files are missing." | |
12 | + | echo "Please make sure anduinos-packages.txt and ubuntu-24-packages.txt are present." | |
13 | + | exit 1 | |
14 | + | fi | |
15 | + | ||
16 | + | echo "===== Packages installed on anduinos but NOT on ubuntu =====" | |
17 | + | comm -23 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt) | |
18 | + | ||
19 | + | echo | |
20 | + | echo "===== Packages installed on ubuntu but NOT on anduinos =====" | |
21 | + | comm -13 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt) | |
22 | + | ||
23 | + | echo | |
24 | + | echo "Comparison done." |
export.sh(file created)
@@ -0,0 +1,2 @@ | |||
1 | + | dpkg-query -f '${binary:Package}\n' -W > anduinos-packages.txt | |
2 | + | dpkg-query -f '${binary:Package}\n' -W > ubuntu-24-packages.txt |