blob: 4724367e2e722d03633b615176c2d7583a525201 [file] [log] [blame]
serge-sans-paille515bc8c2020-07-15 07:31:131#!/usr/bin/env python
Pavel Labath0e44cff2017-06-29 13:02:152
Zachary Turner1d752972017-03-06 17:41:003import argparse
Zachary Turner4dbf9fa2017-03-21 22:46:464import itertools
Zachary Turnere030d102017-03-06 17:40:365import os
6import re
Zachary Turner4dbf9fa2017-03-21 22:46:467import sys
Zachary Turnerbbd17222017-03-22 18:23:148from collections import defaultdict
Zachary Turnere030d102017-03-06 17:40:369
10from use_lldb_suite import lldb_root
11
Zachary Turner1d752972017-03-06 17:41:0012parser = argparse.ArgumentParser(
David Spickett602e47c2023-09-14 07:54:0213 description="Analyze LLDB project #include dependencies."
14)
15parser.add_argument(
16 "--show-counts",
17 default=False,
18 action="store_true",
19 help="When true, show the number of dependencies from each subproject",
20)
21parser.add_argument(
22 "--discover-cycles",
23 default=False,
24 action="store_true",
25 help="When true, find and display all project dependency cycles. Note,"
26 "this option is very slow",
27)
Zachary Turner7e3050c2017-03-20 23:54:2628
Zachary Turner1d752972017-03-06 17:41:0029args = parser.parse_args()
30
Zachary Turnere030d102017-03-06 17:40:3631src_dir = os.path.join(lldb_root, "source")
32inc_dir = os.path.join(lldb_root, "include")
33
34src_map = {}
35
David Spickett602e47c2023-09-14 07:54:0236include_regex = re.compile('#include "((lldb|Plugins|clang)(.*/)+).*"')
37
Zachary Turner1d752972017-03-06 17:41:0038
Zachary Turner7e3050c2017-03-20 23:54:2639def is_sublist(small, big):
Zachary Turner4dbf9fa2017-03-21 22:46:4640 it = iter(big)
Zachary Turner7e3050c2017-03-20 23:54:2641 return all(c in it for c in small)
42
David Spickett602e47c2023-09-14 07:54:0243
Zachary Turner1d752972017-03-06 17:41:0044def normalize_host(str):
45 if str.startswith("lldb/Host"):
46 return "lldb/Host"
Zachary Turner7e3050c2017-03-20 23:54:2647 if str.startswith("Plugins"):
48 return "lldb/" + str
49 if str.startswith("lldb/../../source"):
50 return str.replace("lldb/../../source", "lldb")
Zachary Turner1d752972017-03-06 17:41:0051 return str
Zachary Turnere030d102017-03-06 17:40:3652
David Spickett602e47c2023-09-14 07:54:0253
Zachary Turnere030d102017-03-06 17:40:3654def scan_deps(this_dir, file):
Zachary Turner1d752972017-03-06 17:41:0055 global src_map
56 deps = {}
57 this_dir = normalize_host(this_dir)
58 if this_dir in src_map:
59 deps = src_map[this_dir]
60
Zachary Turnere030d102017-03-06 17:40:3661 with open(file) as f:
62 for line in list(f):
63 m = include_regex.match(line)
Zachary Turner1d752972017-03-06 17:41:0064 if m is None:
65 continue
66 relative = m.groups()[0].rstrip("/")
67 if relative == this_dir:
68 continue
69 relative = normalize_host(relative)
70 if relative in deps:
71 deps[relative] += 1
Zachary Turner7e3050c2017-03-20 23:54:2672 elif relative != this_dir:
Zachary Turner1d752972017-03-06 17:41:0073 deps[relative] = 1
74 if this_dir not in src_map and len(deps) > 0:
75 src_map[this_dir] = deps
Zachary Turnere030d102017-03-06 17:40:3676
David Spickett602e47c2023-09-14 07:54:0277
78for base, dirs, files in os.walk(inc_dir):
Zachary Turnere030d102017-03-06 17:40:3679 dir = os.path.basename(base)
80 relative = os.path.relpath(base, inc_dir)
Serge Guelton32cffcf2019-03-21 07:19:0981 inc_files = [x for x in files if os.path.splitext(x)[1] in [".h"]]
Zachary Turnere030d102017-03-06 17:40:3682 relative = relative.replace("\\", "/")
83 for inc in inc_files:
84 inc_path = os.path.join(base, inc)
Zachary Turner1d752972017-03-06 17:41:0085 scan_deps(relative, inc_path)
Zachary Turnere030d102017-03-06 17:40:3686
David Spickett602e47c2023-09-14 07:54:0287for base, dirs, files in os.walk(src_dir):
Zachary Turnere030d102017-03-06 17:40:3688 dir = os.path.basename(base)
89 relative = os.path.relpath(base, src_dir)
Serge Guelton32cffcf2019-03-21 07:19:0990 src_files = [x for x in files if os.path.splitext(x)[1] in [".cpp", ".h", ".mm"]]
Zachary Turnere030d102017-03-06 17:40:3691 norm_base_path = os.path.normpath(os.path.join("lldb", relative))
92 norm_base_path = norm_base_path.replace("\\", "/")
93 for src in src_files:
94 src_path = os.path.join(base, src)
Zachary Turner1d752972017-03-06 17:41:0095 scan_deps(norm_base_path, src_path)
Zachary Turnere030d102017-03-06 17:40:3696 pass
97
David Spickett602e47c2023-09-14 07:54:0298
Zachary Turner7e3050c2017-03-20 23:54:2699def is_existing_cycle(path, cycles):
100 # If we have a cycle like # A -> B -> C (with an implicit -> A at the end)
101 # then we don't just want to check for an occurrence of A -> B -> C in the
102 # list of known cycles, but every possible rotation of A -> B -> C. For
103 # example, if we previously encountered B -> C -> A (with an implicit -> B
104 # at the end), then A -> B -> C is also a cycle. This is an important
105 # optimization which reduces the search space by multiple orders of
106 # magnitude.
David Spickett602e47c2023-09-14 07:54:02107 for i in range(0, len(path)):
Zachary Turner7e3050c2017-03-20 23:54:26108 if any(is_sublist(x, path) for x in cycles):
109 return True
110 path = [path[-1]] + path[0:-1]
111 return False
112
David Spickett602e47c2023-09-14 07:54:02113
Zachary Turner7e3050c2017-03-20 23:54:26114def expand(path_queue, path_lengths, cycles, src_map):
115 # We do a breadth first search, to make sure we visit all paths in order
116 # of ascending length. This is an important optimization to make sure that
117 # short cycles are discovered first, which will allow us to discard longer
118 # cycles which grow the search space exponentially the longer they get.
119 while len(path_queue) > 0:
120 cur_path = path_queue.pop(0)
121 if is_existing_cycle(cur_path, cycles):
122 continue
123
124 next_len = path_lengths.pop(0) + 1
Zachary Turner7e3050c2017-03-20 23:54:26125 last_component = cur_path[-1]
Zachary Turner84a62182017-03-22 18:04:20126
Pavel Labathe55a0972020-06-30 15:05:08127 for item in src_map.get(last_component, []):
Zachary Turner7e3050c2017-03-20 23:54:26128 if item.startswith("clang"):
129 continue
130
131 if item in cur_path:
132 # This is a cycle. Minimize it and then check if the result is
133 # already in the list of cycles. Insert it (or not) and then
134 # exit.
135 new_index = cur_path.index(item)
136 cycle = cur_path[new_index:]
137 if not is_existing_cycle(cycle, cycles):
138 cycles.append(cycle)
139 continue
140
141 path_lengths.append(next_len)
142 path_queue.append(cur_path + [item])
143 pass
144
David Spickett602e47c2023-09-14 07:54:02145
Zachary Turner7e3050c2017-03-20 23:54:26146cycles = []
147
Pavel Labatha1ff8202018-12-06 10:27:38148path_queue = [[x] for x in iter(src_map)]
Zachary Turner7e3050c2017-03-20 23:54:26149path_lens = [1] * len(path_queue)
150
Pavel Labatha1ff8202018-12-06 10:27:38151items = list(src_map.items())
David Spickett602e47c2023-09-14 07:54:02152items.sort(key=lambda A: A[0])
Zachary Turnere030d102017-03-06 17:40:36153
David Spickett602e47c2023-09-14 07:54:02154for path, deps in items:
Pavel Labatha1ff8202018-12-06 10:27:38155 print(path + ":")
156 sorted_deps = list(deps.items())
Zachary Turner1d752972017-03-06 17:41:00157 if args.show_counts:
David Spickett602e47c2023-09-14 07:54:02158 sorted_deps.sort(key=lambda A: (A[1], A[0]))
Zachary Turner1d752972017-03-06 17:41:00159 for dep in sorted_deps:
Pavel Labatha1ff8202018-12-06 10:27:38160 print("\t{} [{}]".format(dep[0], dep[1]))
Zachary Turner1d752972017-03-06 17:41:00161 else:
David Spickett602e47c2023-09-14 07:54:02162 sorted_deps.sort(key=lambda A: A[0])
Zachary Turner1d752972017-03-06 17:41:00163 for dep in sorted_deps:
Pavel Labatha1ff8202018-12-06 10:27:38164 print("\t{}".format(dep[0]))
Zachary Turner7e3050c2017-03-20 23:54:26165
David Spickett602e47c2023-09-14 07:54:02166
Zachary Turner4dbf9fa2017-03-21 22:46:46167def iter_cycles(cycles):
168 global src_map
169 for cycle in cycles:
170 cycle.append(cycle[0])
171 zipper = list(zip(cycle[0:-1], cycle[1:]))
David Spickett602e47c2023-09-14 07:54:02172 result = [(x, src_map[x][y], y) for (x, y) in zipper]
Zachary Turner4dbf9fa2017-03-21 22:46:46173 total = 0
174 smallest = result[0][1]
David Spickett602e47c2023-09-14 07:54:02175 for first, value, last in result:
Zachary Turner4dbf9fa2017-03-21 22:46:46176 total += value
177 smallest = min(smallest, value)
178 yield (total, smallest, result)
179
David Spickett602e47c2023-09-14 07:54:02180
Zachary Turner7e3050c2017-03-20 23:54:26181if args.discover_cycles:
Pavel Labatha1ff8202018-12-06 10:27:38182 print("Analyzing cycles...")
Zachary Turner7e3050c2017-03-20 23:54:26183
184 expand(path_queue, path_lens, cycles, src_map)
185
David Spickett602e47c2023-09-14 07:54:02186 average = sum([len(x) + 1 for x in cycles]) / len(cycles)
Zachary Turner7e3050c2017-03-20 23:54:26187
Pavel Labatha1ff8202018-12-06 10:27:38188 print("Found {} cycles. Average cycle length = {}.".format(len(cycles), average))
Zachary Turnerbbd17222017-03-22 18:23:14189 counted = list(iter_cycles(cycles))
Zachary Turner4dbf9fa2017-03-21 22:46:46190 if args.show_counts:
David Spickett602e47c2023-09-14 07:54:02191 counted.sort(key=lambda A: A[0])
192 for total, smallest, cycle in counted:
Zachary Turner4dbf9fa2017-03-21 22:46:46193 sys.stdout.write("{} deps to break: ".format(total))
194 sys.stdout.write(cycle[0][0])
David Spickett602e47c2023-09-14 07:54:02195 for first, count, last in cycle:
Zachary Turner4dbf9fa2017-03-21 22:46:46196 sys.stdout.write(" [{}->] {}".format(count, last))
197 sys.stdout.write("\n")
198 else:
199 for cycle in cycles:
200 cycle.append(cycle[0])
Pavel Labatha1ff8202018-12-06 10:27:38201 print(" -> ".join(cycle))
Zachary Turner84a62182017-03-22 18:04:20202
Pavel Labatha1ff8202018-12-06 10:27:38203 print("Analyzing islands...")
Zachary Turner84a62182017-03-22 18:04:20204 islands = []
Zachary Turnerbbd17222017-03-22 18:23:14205 outgoing_counts = defaultdict(int)
206 incoming_counts = defaultdict(int)
David Spickett602e47c2023-09-14 07:54:02207 for total, smallest, cycle in counted:
208 for first, count, last in cycle:
Zachary Turnerbbd17222017-03-22 18:23:14209 outgoing_counts[first] += count
210 incoming_counts[last] += count
Zachary Turner84a62182017-03-22 18:04:20211 for cycle in cycles:
212 this_cycle = set(cycle)
213 disjoints = [x for x in islands if this_cycle.isdisjoint(x)]
214 overlaps = [x for x in islands if not this_cycle.isdisjoint(x)]
215 islands = disjoints + [set.union(this_cycle, *overlaps)]
Pavel Labatha1ff8202018-12-06 10:27:38216 print("Found {} disjoint cycle islands...".format(len(islands)))
Zachary Turner84a62182017-03-22 18:04:20217 for island in islands:
Pavel Labatha1ff8202018-12-06 10:27:38218 print("Island ({} elements)".format(len(island)))
Zachary Turnerbbd17222017-03-22 18:23:14219 sorted = []
Zachary Turner84a62182017-03-22 18:04:20220 for node in island:
Zachary Turnerbbd17222017-03-22 18:23:14221 sorted.append((node, incoming_counts[node], outgoing_counts[node]))
David Spickett602e47c2023-09-14 07:54:02222 sorted.sort(key=lambda x: x[1] + x[2])
223 for node, inc, outg in sorted:
Pavel Labatha1ff8202018-12-06 10:27:38224 print(" {} [{} in, {} out]".format(node, inc, outg))
Zachary Turner4dbf9fa2017-03-21 22:46:46225 sys.stdout.flush()
Pavel Labath0e44cff2017-06-29 13:02:15226pass