summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lldb/scripts/analyze-project-deps.py78
1 files changed, 77 insertions, 1 deletions
diff --git a/lldb/scripts/analyze-project-deps.py b/lldb/scripts/analyze-project-deps.py
index 38b375789aa..1f7b5f393b9 100644
--- a/lldb/scripts/analyze-project-deps.py
+++ b/lldb/scripts/analyze-project-deps.py
@@ -8,6 +8,10 @@ parser = argparse.ArgumentParser(
description='Analyze LLDB project #include dependencies.')
parser.add_argument('--show-counts', default=False, action='store_true',
help='When true, show the number of dependencies from each subproject')
+parser.add_argument('--discover-cycles', default=False, action='store_true',
+ help='When true, find and display all project dependency cycles. Note,'
+ 'this option is very slow')
+
args = parser.parse_args()
src_dir = os.path.join(lldb_root, "source")
@@ -17,9 +21,17 @@ src_map = {}
include_regex = re.compile('#include \"((lldb|Plugins|clang)(.*/)+).*\"')
+def is_sublist(small, big):
+ it = iter(big)
+ return all(c in it for c in small)
+
def normalize_host(str):
if str.startswith("lldb/Host"):
return "lldb/Host"
+ if str.startswith("Plugins"):
+ return "lldb/" + str
+ if str.startswith("lldb/../../source"):
+ return str.replace("lldb/../../source", "lldb")
return str
def scan_deps(this_dir, file):
@@ -40,7 +52,7 @@ def scan_deps(this_dir, file):
relative = normalize_host(relative)
if relative in deps:
deps[relative] += 1
- else:
+ elif relative != this_dir:
deps[relative] = 1
if this_dir not in src_map and len(deps) > 0:
src_map[this_dir] = deps
@@ -65,6 +77,57 @@ for (base, dirs, files) in os.walk(src_dir):
scan_deps(norm_base_path, src_path)
pass
+def is_existing_cycle(path, cycles):
+ # If we have a cycle like # A -> B -> C (with an implicit -> A at the end)
+ # then we don't just want to check for an occurrence of A -> B -> C in the
+ # list of known cycles, but every possible rotation of A -> B -> C. For
+ # example, if we previously encountered B -> C -> A (with an implicit -> B
+ # at the end), then A -> B -> C is also a cycle. This is an important
+ # optimization which reduces the search space by multiple orders of
+ # magnitude.
+ for i in xrange(0,len(path)):
+ if any(is_sublist(x, path) for x in cycles):
+ return True
+ path = [path[-1]] + path[0:-1]
+ return False
+
+def expand(path_queue, path_lengths, cycles, src_map):
+ # We do a breadth first search, to make sure we visit all paths in order
+ # of ascending length. This is an important optimization to make sure that
+ # short cycles are discovered first, which will allow us to discard longer
+ # cycles which grow the search space exponentially the longer they get.
+ while len(path_queue) > 0:
+ cur_path = path_queue.pop(0)
+ if is_existing_cycle(cur_path, cycles):
+ continue
+
+ next_len = path_lengths.pop(0) + 1
+
+ last_component = cur_path[-1]
+
+ for item in src_map[last_component]:
+ if item.startswith("clang"):
+ continue
+
+ if item in cur_path:
+ # This is a cycle. Minimize it and then check if the result is
+ # already in the list of cycles. Insert it (or not) and then
+ # exit.
+ new_index = cur_path.index(item)
+ cycle = cur_path[new_index:]
+ if not is_existing_cycle(cycle, cycles):
+ cycles.append(cycle)
+ continue
+
+ path_lengths.append(next_len)
+ path_queue.append(cur_path + [item])
+ pass
+
+cycles = []
+
+path_queue = [[x] for x in src_map.iterkeys()]
+path_lens = [1] * len(path_queue)
+
items = list(src_map.iteritems())
items.sort(lambda A, B : cmp(A[0], B[0]))
@@ -79,4 +142,17 @@ for (path, deps) in items:
sorted_deps.sort(lambda A, B: cmp(A[0], B[0]))
for dep in sorted_deps:
print "\t{}".format(dep[0])
+
+if args.discover_cycles:
+ print "Analyzing cycles..."
+
+ expand(path_queue, path_lens, cycles, src_map)
+
+ average = sum([len(x)+1 for x in cycles]) / len(cycles)
+
+ print "Found {} cycles. Average cycle length = {}.".format(len(cycles), average)
+ for cycle in cycles:
+ cycle.append(cycle[0])
+ print " -> ".join(cycle)
+
pass \ No newline at end of file
OpenPOWER on IntegriCloud