Geant4-11
geant4_check_module_cycles.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3"""Check Geant4's module dependency graph for cycles
4
5Geant4 organises source code into "Modules", with each Module being a
6directory containing headers and sources in `include/` and `src/`
7subdirectories respectively. One or more Modules are grouped/compiled
8into the actual libraries.
9
10CMake will raise errors if there are circular dependencies between
11shared libraries (cycles are allowed between static libraries). However,
12CMake cannot detect cycles between Modules directly, are these are a
13Geant4 construct. Whilst cycles between modules that get added to the
14same library are technically o.k., they still indicate either:
15
16- A bad design/organisation of code
17- A mistake in the declared dependencies for a module(s)
18
19To help developers and CI pick module cycles, Geant4's CMake scripts
20write out the declared module dependencies in a text file as an Adjacency
21List graph. This comprises one line per module, with the first column
22being the module name, and any remaining columns being the modules the
23first one "depends on", i.e. uses code from.
24
25This program reads that file and uses Python's graphlib module to try
26topologically sorting this graph. Failure to sort indicates a cycle, and
27this will be printed to stdout and a non-zero return code emitted on exit.
28"""
29
30import argparse
31import graphlib
32import sys
33
34if __name__ == "__main__":
35 # Parse command line to get file to load
36 parser = argparse.ArgumentParser(
37 description=str(__doc__), formatter_class=argparse.RawDescriptionHelpFormatter
38 )
39 parser.add_argument(
40 "-f",
41 "--file",
42 required=True,
43 metavar="FILE",
44 help="file to read adjacency list from",
45 )
46 parser.add_argument(
47 "-v",
48 "--verbose",
49 action="store_true",
50 help="print topologically sorted list of modules",
51 )
52 args = parser.parse_args()
53
54 try:
55 # 1. Try and construct graph from input adjancey list file
56 adjList = {}
57 with open(args.file) as f:
58 for line in f:
59 if not line.strip().startswith("#"):
60 nodes = line.rstrip("\n").split(" ")
61 adjList[nodes[0]] = nodes[1:]
62
63 if not adjList:
64 print(f"warning: graph read from '{args.file}' is empty")
65
66 # NB also possible with networkx, but requires pip install:
67 # G = nx.read_adjlist(args.file, create_using=nx.DiGraph)
68 # cycles = nx.find_cycle(G, orientation='original')
69
70 # Topo sort throws cycle error if one occurs during prepare
71 ts = graphlib.TopologicalSorter(adjList)
72 ts.prepare()
73
74 # Verbose print of nodes in topological order
75 # NB: This uses the full graphlib interface in case we want to
76 # print out nodes grouped by level in the graph.
77 if args.verbose:
78 while ts.is_active():
79 nodes = ts.get_ready()
80 for n in nodes:
81 print(n)
82 ts.done(*nodes)
83
84 print(f"pass: No cycles detected in graph defined in '{args.file}'")
85
86 except OSError as err:
87 print(f"OS error: {err}")
88 sys.exit(1)
89 except graphlib.CycleError as err:
90 print(
91 f"error: cycles detected in input graph from '{args.file}':",
92 file=sys.stderr,
93 )
94 cycle = " <- ".join(err.args[1])
95 print(f"cycle: {cycle}", file=sys.stderr)
96 sys.exit(1)
void print(G4double elem)
String_V split(const G4String &input, char separator='\n')