15 from __future__
import print_function
18 from os.path
import basename
19 import subprocess
as sp
22 import PyUtils.Dso
as Dso
25 """Global cache of already processed files"""
35 """Add file to local and global cache"""
37 Cache.files[shlib.lib] = shlib
40 for d
in self.
dotsrc:
print (file, d, file=file)
42 def dot(self, code, style={}):
43 """Output a line of dot code"""
46 for k,v
in style.items():
47 code +=
'%s="%s" ' % (k,v)
55 """Represent a shared library with name, dependencies and other stats"""
59 lib = osp.expanduser(osp.expandvars(lib))
60 if not osp.exists(lib):
61 l = Dso.find_library(lib)
64 assert osp.exists(lib),
"no such path [%s]" % (lib,)
70 """Get direct dependencies of shared library"""
73 cachedlib = Cache.files.get(lib)
74 if cachedlib:
return cachedlib.deplibs
78 encargs = {
'encoding' :
'utf-8'}
79 p = sp.Popen([
"readelf",
"-d",lib], stdout=sp.PIPE, **encargs)
80 output = p.communicate()[0]
82 print (
"Cannot run 'readelf' on",lib)
86 for l
in output.split(
"\n"):
87 if l.find(
"NEEDED")==-1:
continue
88 libs += [l.split()[-1].strip(
"[]")]
91 p = sp.Popen([
"ldd",lib], stdout=sp.PIPE, **encargs)
92 output = p.communicate()[0]
94 print (
"Cannot run 'ldd' on",lib)
98 for l
in output.split(
"\n"):
99 fields = l.strip().
split()
100 if len(fields)!=4:
continue
102 if (fields[0]
in libs)
and len(path)>0:
109 """Helper class for colored nodes"""
113 projects = {
"DetCommon" : 2,
116 "AtlasConditions" : 4,
117 "AtlasReconstruction" : 5,
118 "AtlasSimulation" : 6,
126 if lib.find(p)!=-1:
return "/%s/%s" % (cls.
scheme, c)
131 """Statistics calculated from Cache object"""
138 for lib
in cache.myfiles.values():
143 def anaLib(lib, opt, cache, select = [], ignore = [], depth = 0):
144 """Get dependencies of shared library recursively"""
147 """Should this lib be processed?"""
148 for regexp
in select:
149 if regexp.match(path):
return True
150 if len(select)>0:
return False
152 for regexp
in ignore:
153 if regexp.match(path):
return False
156 if opt.maxdepth
and depth>=opt.maxdepth:
return
159 cachedlib = cache.myfiles.get(lib)
162 if depth<cachedlib.distance: cachedlib.distance=depth
168 for l
in shlib.deplibs:
171 anaLib(l, opt, cache, select, ignore, depth+1)
177 """Process one library"""
182 dot(
'digraph DependencyTree {')
183 dot(
' ratio=0.9 nodesep=0.05')
189 for f
in opt.filter: select += [re.compile(f)]
191 select = [re.compile(
".*atlas/software.*")]
193 anaLib(lib, opt, cache, select, ignore)
196 for l,v
in cache.myfiles.items():
200 style[
"shape"] =
"box"
203 style[
"style"] =
"filled"
204 style[
"fillcolor"] = Color.get(l)
211 if dotFileName: outFile =
open(dotFileName,
"w")
214 cache.writeDOT(outFile)
218 st =
Stats(lib, cache)
227 """Print statistics"""
230 print (
"%-50s %7s %7s" % (
"Library dependencies",
"Direct",
"Total"))
232 for s
in sorted(Cache.stats, key=operator.attrgetter(
"depDirect"), reverse=
True):
233 print (
"%-50s %7d %7d" % (
basename(s.lib), s.depDirect, s.depTotal))
241 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.",
242 usage=
"%prog [OPTIONS] LIB [LIB...]")
244 parser.add_option(
"-o",
"--output",
245 help=
"File for DOT source code (default is LIB.dot)")
247 parser.add_option(
"-d",
"--maxdepth", type=
"int",
248 help=
"Maximum depth of dependency tree [1..]")
250 parser.add_option(
"-f",
"--filter", action=
"append",
251 help=
"Only analyze libraries matching regular expression (can be specified multiple times) [default: .*atlas/software.*]")
253 parser.add_option(
"--nocolor", action=
"store_true",
254 help=
"Do not use colors")
256 parser.add_option(
"-s",
"--stats", action=
"store_true",
257 help=
"Print statistics")
259 (opt, args) = parser.parse_args()
261 parser.error(
"Invalid number of arguments specified")
264 if len(args)>1
and opt.output:
265 print (
"Multiple libraries specified. Ignoring output file name.")
277 if __name__ ==
"__main__":
280 except KeyboardInterrupt: