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