8
8
description = 'Analyze LLDB project #include dependencies.' )
9
9
parser .add_argument ('--show-counts' , default = False , action = 'store_true' ,
10
10
help = 'When true, show the number of dependencies from each subproject' )
11
+ parser .add_argument ('--discover-cycles' , default = False , action = 'store_true' ,
12
+ help = 'When true, find and display all project dependency cycles. Note,'
13
+ 'this option is very slow' )
14
+
11
15
args = parser .parse_args ()
12
16
13
17
src_dir = os .path .join (lldb_root , "source" )
17
21
18
22
include_regex = re .compile ('#include \" ((lldb|Plugins|clang)(.*/)+).*\" ' )
19
23
24
+ def is_sublist (small , big ):
25
+ it = iter (big )
26
+ return all (c in it for c in small )
27
+
20
28
def normalize_host (str ):
21
29
if str .startswith ("lldb/Host" ):
22
30
return "lldb/Host"
31
+ if str .startswith ("Plugins" ):
32
+ return "lldb/" + str
33
+ if str .startswith ("lldb/../../source" ):
34
+ return str .replace ("lldb/../../source" , "lldb" )
23
35
return str
24
36
25
37
def scan_deps (this_dir , file ):
@@ -40,7 +52,7 @@ def scan_deps(this_dir, file):
40
52
relative = normalize_host (relative )
41
53
if relative in deps :
42
54
deps [relative ] += 1
43
- else :
55
+ elif relative != this_dir :
44
56
deps [relative ] = 1
45
57
if this_dir not in src_map and len (deps ) > 0 :
46
58
src_map [this_dir ] = deps
@@ -65,6 +77,57 @@ def scan_deps(this_dir, file):
65
77
scan_deps (norm_base_path , src_path )
66
78
pass
67
79
80
+ def is_existing_cycle (path , cycles ):
81
+ # If we have a cycle like # A -> B -> C (with an implicit -> A at the end)
82
+ # then we don't just want to check for an occurrence of A -> B -> C in the
83
+ # list of known cycles, but every possible rotation of A -> B -> C. For
84
+ # example, if we previously encountered B -> C -> A (with an implicit -> B
85
+ # at the end), then A -> B -> C is also a cycle. This is an important
86
+ # optimization which reduces the search space by multiple orders of
87
+ # magnitude.
88
+ for i in xrange (0 ,len (path )):
89
+ if any (is_sublist (x , path ) for x in cycles ):
90
+ return True
91
+ path = [path [- 1 ]] + path [0 :- 1 ]
92
+ return False
93
+
94
+ def expand (path_queue , path_lengths , cycles , src_map ):
95
+ # We do a breadth first search, to make sure we visit all paths in order
96
+ # of ascending length. This is an important optimization to make sure that
97
+ # short cycles are discovered first, which will allow us to discard longer
98
+ # cycles which grow the search space exponentially the longer they get.
99
+ while len (path_queue ) > 0 :
100
+ cur_path = path_queue .pop (0 )
101
+ if is_existing_cycle (cur_path , cycles ):
102
+ continue
103
+
104
+ next_len = path_lengths .pop (0 ) + 1
105
+
106
+ last_component = cur_path [- 1 ]
107
+
108
+ for item in src_map [last_component ]:
109
+ if item .startswith ("clang" ):
110
+ continue
111
+
112
+ if item in cur_path :
113
+ # This is a cycle. Minimize it and then check if the result is
114
+ # already in the list of cycles. Insert it (or not) and then
115
+ # exit.
116
+ new_index = cur_path .index (item )
117
+ cycle = cur_path [new_index :]
118
+ if not is_existing_cycle (cycle , cycles ):
119
+ cycles .append (cycle )
120
+ continue
121
+
122
+ path_lengths .append (next_len )
123
+ path_queue .append (cur_path + [item ])
124
+ pass
125
+
126
+ cycles = []
127
+
128
+ path_queue = [[x ] for x in src_map .iterkeys ()]
129
+ path_lens = [1 ] * len (path_queue )
130
+
68
131
items = list (src_map .iteritems ())
69
132
items .sort (lambda A , B : cmp (A [0 ], B [0 ]))
70
133
@@ -79,4 +142,17 @@ def scan_deps(this_dir, file):
79
142
sorted_deps .sort (lambda A , B : cmp (A [0 ], B [0 ]))
80
143
for dep in sorted_deps :
81
144
print "\t {}" .format (dep [0 ])
145
+
146
+ if args .discover_cycles :
147
+ print "Analyzing cycles..."
148
+
149
+ expand (path_queue , path_lens , cycles , src_map )
150
+
151
+ average = sum ([len (x )+ 1 for x in cycles ]) / len (cycles )
152
+
153
+ print "Found {} cycles. Average cycle length = {}." .format (len (cycles ), average )
154
+ for cycle in cycles :
155
+ cycle .append (cycle [0 ])
156
+ print " -> " .join (cycle )
157
+
82
158
pass
0 commit comments