16from os.path
import basename
17import subprocess
as sp
20import 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():
141def 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")
210 else: outFile = open(
basename(lib)+
".dot",
"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.")
275if __name__ ==
"__main__":
278 except KeyboardInterrupt:
__init__(self, distance, lib)
__init__(self, lib, cache)
const std::string process
std::vector< std::string > split(const std::string &s, const std::string &t=":")
processLib(lib, opt, dotFileName=None)
anaLib(lib, opt, cache, select=[], ignore=[], depth=0)
std::string basename(std::string name)