16 from os.path
import basename
17 import subprocess
as sp
20 import PyUtils.Dso
as Dso
23 """Global cache of already processed files"""
33 """Add file to local and global cache"""
35 Cache.files[shlib.lib] = shlib
38 for d
in self.
dotsrc:
print (file, d, file=file)
40 def dot(self, code, style={}):
41 """Output a line of dot code"""
44 for k,v
in style.items():
45 code +=
'%s="%s" ' % (k,v)
53 """Represent a shared library with name, dependencies and other stats"""
57 lib = osp.expanduser(osp.expandvars(lib))
58 if not osp.exists(lib):
59 l = Dso.find_library(lib)
62 assert osp.exists(lib),
"no such path [%s]" % (lib,)
68 """Get direct dependencies of shared library"""
71 cachedlib = Cache.files.get(lib)
72 if cachedlib:
return cachedlib.deplibs
76 encargs = {
'encoding' :
'utf-8'}
77 p = sp.Popen([
"readelf",
"-d",lib], stdout=sp.PIPE, **encargs)
78 output = p.communicate()[0]
80 print (
"Cannot run 'readelf' on",lib)
84 for l
in output.split(
"\n"):
85 if l.find(
"NEEDED")==-1:
continue
86 libs += [l.split()[-1].strip(
"[]")]
89 p = sp.Popen([
"ldd",lib], stdout=sp.PIPE, **encargs)
90 output = p.communicate()[0]
92 print (
"Cannot run 'ldd' on",lib)
96 for l
in output.split(
"\n"):
97 fields = l.strip().
split()
98 if len(fields)!=4:
continue
100 if (fields[0]
in libs)
and len(path)>0:
107 """Helper class for colored nodes"""
111 projects = {
"DetCommon" : 2,
114 "AtlasConditions" : 4,
115 "AtlasReconstruction" : 5,
116 "AtlasSimulation" : 6,
124 if lib.find(p)!=-1:
return "/%s/%s" % (cls.
scheme, c)
129 """Statistics calculated from Cache object"""
136 for lib
in cache.myfiles.values():
141 def anaLib(lib, opt, cache, select = [], ignore = [], depth = 0):
142 """Get dependencies of shared library recursively"""
145 """Should this lib be processed?"""
146 for regexp
in select:
147 if regexp.match(path):
return True
148 if len(select)>0:
return False
150 for regexp
in ignore:
151 if regexp.match(path):
return False
154 if opt.maxdepth
and depth>=opt.maxdepth:
return
157 cachedlib = cache.myfiles.get(lib)
160 if depth<cachedlib.distance: cachedlib.distance=depth
166 for l
in shlib.deplibs:
169 anaLib(l, opt, cache, select, ignore, depth+1)
175 """Process one library"""
180 dot(
'digraph DependencyTree {')
181 dot(
' ratio=0.9 nodesep=0.05')
187 for f
in opt.filter: select += [re.compile(f)]
189 select = [re.compile(
".*atlas/software.*")]
191 anaLib(lib, opt, cache, select, ignore)
194 for l,v
in cache.myfiles.items():
198 style[
"shape"] =
"box"
201 style[
"style"] =
"filled"
202 style[
"fillcolor"] = Color.get(l)
209 if dotFileName: outFile =
open(dotFileName,
"w")
212 cache.writeDOT(outFile)
216 st =
Stats(lib, cache)
225 """Print statistics"""
228 print (
"%-50s %7s %7s" % (
"Library dependencies",
"Direct",
"Total"))
230 for s
in sorted(Cache.stats, key=operator.attrgetter(
"depDirect"), reverse=
True):
231 print (
"%-50s %7d %7d" % (
basename(s.lib), s.depDirect, s.depTotal))
239 parser = optparse.OptionParser(description=
"Create runtime dependecy graph for shared library. The output is a graph in DOT language. To visualize it use, e.g. 'dot -O -Tps mygraph.dot'. The rectangular nodes represent direct dependencies. Nodes belonging to the same project have the same color.",
240 usage=
"%prog [OPTIONS] LIB [LIB...]")
242 parser.add_option(
"-o",
"--output",
243 help=
"File for DOT source code (default is LIB.dot)")
245 parser.add_option(
"-d",
"--maxdepth", type=
"int",
246 help=
"Maximum depth of dependency tree [1..]")
248 parser.add_option(
"-f",
"--filter", action=
"append",
249 help=
"Only analyze libraries matching regular expression (can be specified multiple times) [default: .*atlas/software.*]")
251 parser.add_option(
"--nocolor", action=
"store_true",
252 help=
"Do not use colors")
254 parser.add_option(
"-s",
"--stats", action=
"store_true",
255 help=
"Print statistics")
257 (opt, args) = parser.parse_args()
259 parser.error(
"Invalid number of arguments specified")
262 if len(args)>1
and opt.output:
263 print (
"Multiple libraries specified. Ignoring output file name.")
275 if __name__ ==
"__main__":
278 except KeyboardInterrupt: