forked from dashpay/dash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcircular-dependencies.py
executable file
·123 lines (100 loc) · 3.82 KB
/
circular-dependencies.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env python3
# Copyright (c) 2018-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import sys
import re
from multiprocess import Pool
MAPPING = {
'core_read.cpp': 'core_io.cpp',
'core_write.cpp': 'core_io.cpp',
}
# Directories with header-based modules, where the assumption that .cpp files
# define functions and variables declared in corresponding .h files is
# incorrect.
HEADER_MODULE_PATHS = [
'interfaces/'
]
def module_name(path):
if path in MAPPING:
path = MAPPING[path]
if any(path.startswith(dirpath) for dirpath in HEADER_MODULE_PATHS):
return path
if path.endswith(".h"):
return path[:-2]
if path.endswith(".c"):
return path[:-2]
if path.endswith(".cpp"):
return path[:-4]
return None
if __name__=="__main__":
files = dict()
deps = dict()
RE = re.compile("^#include <(.*)>")
def handle_module(arg):
module = module_name(arg)
if module is None:
print("Ignoring file %s (does not constitute module)\n" % arg)
else:
files[arg] = module
deps[module] = set()
def handle_module2(module):
# Build the transitive closure of dependencies of module
closure = dict()
for dep in deps[module]:
closure[dep] = []
while True:
old_size = len(closure)
old_closure_keys = sorted(closure.keys())
for src in old_closure_keys:
for dep in deps[src]:
if dep not in closure:
closure[dep] = closure[src] + [src]
if len(closure) == old_size:
break
# If module is in its own transitive closure, it's a circular dependency; check if it is the shortest
if module in closure:
return [module] + closure[module]
return None
# Iterate over files, and create list of modules
for arg in sys.argv[1:]:
handle_module(arg)
def build_list_direct(arg):
module = files[arg]
with open(arg, 'r', encoding="utf8") as f:
for line in f:
match = RE.match(line)
if match:
include = match.group(1)
included_module = module_name(include)
if included_module is not None and included_module in deps and included_module != module:
deps[module].add(included_module)
# Iterate again, and build list of direct dependencies for each module
# TODO: implement support for multiple include directories
for arg in sorted(files.keys()):
build_list_direct(arg)
# Loop to find the shortest (remaining) circular dependency
def shortest_c_dep():
have_cycle = False
sorted_keys = None
while True:
shortest_cycles = None
if sorted_keys is None:
sorted_keys = sorted(deps.keys())
with Pool(8) as pool:
cycles = pool.map(handle_module2, sorted_keys)
for cycle in cycles:
if cycle is not None and (shortest_cycles is None or len(cycle) < len(shortest_cycles)):
shortest_cycles = cycle
if shortest_cycles is None:
break
# We have the shortest circular dependency; report it
module = shortest_cycles[0]
print("Circular dependency: %s" % (" -> ".join(shortest_cycles + [module])))
# And then break the dependency to avoid repeating in other cycles
deps[shortest_cycles[-1]] -= {module}
sorted_keys = None
have_cycle = True
if have_cycle:
return True
sys.exit(1 if shortest_c_dep() else 0)