CMS 3D CMS Logo

cmssw_cycle_finder.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # find cycles in cmssw libs
4 import collections
5 
6 class Graph(object):
7  def __init__(self, edges):
8  self.edges = edges
9 
10  @staticmethod
12  adj = collections.defaultdict(list)
13  for edge in edges:
14  adj[edge[0]].append(edge[1])
15  return adj
16 
17  def addEdge(self,edge):
18  self.edges.append(edge)
19 
21  self.adj = Graph._build_adjacency_list(self.edges)
22 
23 
24 def dfs(G):
25  discovered = set()
26  finished = set()
27  for u in G.adj:
28  if u not in discovered and u not in finished:
29  discovered, finished = dfs_visit(G, u, discovered, finished)
30 
31 def dfs_visit(G, u, discovered, finished):
32  if u not in G.adj:
33  finished.add(u)
34  return discovered, finished
35 
36  discovered.add(u)
37 
38  for v in G.adj[u]:
39  # Detect cycles
40  if v in discovered:
41  print(f"Cycle detected: found a back edge from {u} to {v}. It involves")
42  for i,d in enumerate(discovered):
43  if i != len(discovered)-1:
44  print(d,end=', ')
45  else:
46  print(d)
47 
48  # Recurse into DFS tree
49  else:
50  if v not in finished:
51  dfs_visit(G, v, discovered, finished)
52 
53  discovered.remove(u)
54  finished.add(u)
55 
56  return discovered, finished
57 
58 import subprocess
59 def run_command(comm):
60  encoding = 'utf-8'
61  proc = subprocess.Popen(comm, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
62  stdout, stderr = proc.communicate()
63  stdout = stdout.decode(encoding)
64  if stderr is not None:
65  stderr= stderr.decode(encoding)
66  return proc.returncode, stdout, stderr
67 
68 
69 import os
71  src_base = os.environ.get('CMSSW_RELEASE_BASE')
72  src_path = os.path.join(src_base,"src")
73  comm = "ls -d "+src_path+"/*/*/interface"
74  c_rt,c_out,c_err = run_command(comm)
75 
76  ret_val=[]
77  for l in c_out.split():
78  sp=l.split('/')
79  ret_val.append('/'.join(sp[-3:-1]))
80 
81  return ret_val
82 
83 def get_files(pack,subdir,file_types):
84  src_base = os.environ.get('CMSSW_RELEASE_BASE')
85  pack_path = os.path.join(src_base,"src",pack,subdir)
86  if not os.path.exists(pack_path): return []
87  ret_val=[]
88  for root, dirs, files in os.walk(pack_path, topdown=False):
89  for name in files:
90  for t in file_types:
91  if name.endswith(t):
92  ret_val.append(os.path.join(root,name))
93 
94  return ret_val
95 
96 def get_lib_deps(lib_info):
97  lib_path = lib_info[1]
98  comm = "ldd "+lib_path+"| grep cms | grep -v \"/external\" | grep -v \"/lcg/\" | awk '{print $3}'"
99  c_rt,c_out,c_err = run_command(comm)
100  ret_val=[]
101  for l in c_out.split():
102  lib = l.strip()
103  ret_val.append( (lib.split('/')[-1],lib))
104  return ret_val
105 
106 def get_include_packages(file_list,package=None,is_cuda=False):
107  incs={}
108  pack_incs={}
109  if is_cuda:
110  comm= "gcc -fpreprocessed -dD -E "
111  else:
112  comm= "nvcc --compiler-options -fpreprocessed -dD -E "
113  for f in file_list:
114  comm=comm+f+" "
115  comm=comm+" | grep \"#include\""
116  c_rt,c_out,c_err = run_command(comm)
117  for l in c_out.split():
118  inc = l.strip().split()[-1][1:-1]
119  if '/' in inc:
120  incs['/'.join(inc.split('/')[0:2])]=1
121  if package is not None and package in inc and "interface" in inc:
122  pack_incs[os.path.join('/'.join(file_list[0].split('/')[0:-4]),inc)]=1
123  if package is None:
124  return list(incs.keys())
125  else:
126  return list(incs.keys()),list(pack_incs.keys())
127 
128 
129 
130 import sys
131 
132 if __name__ == "__main__":
133 
134 
135  import argparse
136  parser=argparse.ArgumentParser(description="CMSSW Cyclic dependency finder")
137  parser.add_argument("--omit_header_only",dest="omit_header_only",
138  action="store_false", default=True,
139  help="Ignore cycles due to header only dependencies"
140  )
141  parser.add_argument("--status_bar",dest="status_bar",
142  action="store_true", default=False,
143  help="Show progress bar when running"
144  )
145 
146  args = parser.parse_args()
147  omit_header_only=args.omit_header_only
148  show_status_bar=args.status_bar
149  print(omit_header_only,show_status_bar)
150  if 'CMSSW_RELEASE_BASE' not in os.environ:
151  print("Execute within a cmssw environment")
152  sys.exit(1)
153 
154  G = Graph([])
155 
156  lib_list = get_pack_list()
157 
158  if show_status_bar:
159  import tqdm
160  iter_lib = tqdm.tqdm(lib_list)
161  else:
162  iter_lib = lib_list
163  for lib in iter_lib:
164  header_list = get_files(lib,"interface",[".h",".hpp"])
165  source_list = get_files(lib,"src",[".cc",".cpp",".cxx"])
166  cuda_source_list = get_files(lib,"src",[".cu"])
167 
168  cpp_incs_packages, cpp_self_headers = get_include_packages(source_list,lib)
169  cuda_incs_packages, cuda_self_headers = get_include_packages(cuda_source_list,lib,is_cuda=True)
170 
171  source_incs_packages = list(cpp_incs_packages)
172  source_incs_packages.extend(x for x in cuda_incs_packages if x not in source_incs_packages)
173  self_headers = list(cpp_self_headers)
174  self_headers.extend(x for x in cuda_self_headers if x not in self_headers)
175 
176  if not omit_header_only:
177  header_incs_packages = get_include_packages(header_list)
178  else:
179  header_incs_packages = get_include_packages(self_headers)
180 
181  for dep in header_incs_packages:
182  if dep != lib:
183  G.addEdge( (lib,dep) )
184  for dep in source_incs_packages:
185  if dep != lib and dep not in header_incs_packages:
186  G.addEdge( (lib,dep) )
187 
188  print("Building adjacency graph")
189  G.build_adjacency_list()
190  print("Looking for cycles")
191  dfs(G)
192 
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
static std::string join(char **cmd)
Definition: RemoteFile.cc:21
def dfs_visit(G, u, discovered, finished)
def get_files(pack, subdir, file_types)
def get_lib_deps(lib_info)
def get_include_packages(file_list, package=None, is_cuda=False)