Skip to content

Commit 035dda7

Browse files
committed
Introduce circular-dependencies.py
This script can be used to find circular include dependencies in the code base. These should eventually be weeded out.
1 parent 70721b4 commit 035dda7

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import re
5+
6+
MAPPING = {
7+
'core_read.cpp': 'core_io.cpp',
8+
'core_write.cpp': 'core_io.cpp',
9+
}
10+
11+
# Directories with header-based modules, where the assumption that .cpp files
12+
# define functions and variables declared in corresponding .h files is
13+
# incorrect.
14+
HEADER_MODULE_PATHS = [
15+
'interfaces/'
16+
]
17+
18+
def module_name(path):
19+
if path in MAPPING:
20+
path = MAPPING[path]
21+
if any(path.startswith(dirpath) for dirpath in HEADER_MODULE_PATHS):
22+
return path
23+
if path.endswith(".h"):
24+
return path[:-2]
25+
if path.endswith(".c"):
26+
return path[:-2]
27+
if path.endswith(".cpp"):
28+
return path[:-4]
29+
return None
30+
31+
files = dict()
32+
deps = dict()
33+
34+
RE = re.compile("^#include \"(.*)\"")
35+
36+
# Iterate over files, and create list of modules
37+
for arg in sys.argv[1:]:
38+
module = module_name(arg)
39+
if module is None:
40+
print("Ignoring file %s (does not constitute module)\n" % arg)
41+
else:
42+
files[arg] = module
43+
deps[module] = set()
44+
45+
# Iterate again, and build list of direct dependencies for each module
46+
# TODO: implement support for multiple include directories
47+
for arg in sorted(files.keys()):
48+
module = files[arg]
49+
with open(arg, 'r', encoding="utf8") as f:
50+
for line in f:
51+
match = RE.match(line)
52+
if match:
53+
include = match.group(1)
54+
included_module = module_name(include)
55+
if included_module is not None and included_module in deps and included_module != module:
56+
deps[module].add(included_module)
57+
58+
# Loop to find the shortest (remaining) circular dependency
59+
have_cycle = False
60+
while True:
61+
shortest_cycle = None
62+
for module in sorted(deps.keys()):
63+
# Build the transitive closure of dependencies of module
64+
closure = dict()
65+
for dep in deps[module]:
66+
closure[dep] = []
67+
while True:
68+
old_size = len(closure)
69+
old_closure_keys = sorted(closure.keys())
70+
for src in old_closure_keys:
71+
for dep in deps[src]:
72+
if dep not in closure:
73+
closure[dep] = closure[src] + [src]
74+
if len(closure) == old_size:
75+
break
76+
# If module is in its own transitive closure, it's a circular dependency; check if it is the shortest
77+
if module in closure and (shortest_cycle is None or len(closure[module]) + 1 < len(shortest_cycle)):
78+
shortest_cycle = [module] + closure[module]
79+
if shortest_cycle is None:
80+
break
81+
# We have the shortest circular dependency; report it
82+
module = shortest_cycle[0]
83+
print("Circular dependency: %s" % (" -> ".join(shortest_cycle + [module])))
84+
# And then break the dependency to avoid repeating in other cycles
85+
deps[shortest_cycle[-1]] = deps[shortest_cycle[-1]] - set([module])
86+
have_cycle = True
87+
88+
sys.exit(1 if have_cycle else 0)

0 commit comments

Comments
 (0)