]>
Commit | Line | Data |
---|---|---|
ea1f7807 | 1 | #!/usr/bin/python |
2 | ||
3 | import sys | |
4 | import os | |
5 | import re | |
6 | import string | |
7 | import getopt | |
8 | ||
9 | """ | |
10 | Given a directory, will look into lib*.pkg files to produce a dependency graph | |
11 | of libraries (and DA if there are some) | |
ea1f7807 | 12 | """ |
13 | ||
14 | __author__ = "L. Aphecetche aphecetc_at_in2p3_dot_fr" | |
48739cf2 | 15 | __version__ = "$Id$" |
ea1f7807 | 16 | |
58e0ac49 | 17 | notassociatedfiles = {} |
18 | ||
ea1f7807 | 19 | #_______________________________________________________________________________ |
20 | def usage(): | |
21 | """Describe usage of script | |
22 | """ | |
58e0ac49 | 23 | print "Usage: %s [-h | --help] [-d | --debug] [--da] directory_to_scan" % sys.argv[0] |
ea1f7807 | 24 | sys.exit(1) |
25 | ||
26 | #_______________________________________________________________________________ | |
58e0ac49 | 27 | def append(list,a): |
28 | """ append a to list, if a not there yet | |
ea1f7807 | 29 | """ |
58e0ac49 | 30 | if not a in list: |
31 | list.append(a) | |
32 | ||
ea1f7807 | 33 | |
58e0ac49 | 34 | #_______________________________________________________________________________ |
35 | def getSourceFiles(lib,rootsys,alice_root): | |
36 | """Extract the list of files from a libXXX.pkg file | |
37 | Return a pair of list (sourceFiles,einclude), where einclude | |
38 | is the list of directories needed to be included compile the files. | |
39 | """ | |
40 | ||
41 | # list of possible .pkg variables | |
42 | pkgkeys = [ "SRCS","EINCLUDE","HDRS","FSRCS","DHDR","CSRCS","CHDRS","ELIBS","EDEFINE","PACKFFLAGS","PACKCXXFLAGS","PACKCFLAGS","PACKSOFLAGS","EXPORT","EHDRS" ] | |
43 | ||
44 | keySRCS = pkgkeys[0] | |
45 | keyEINCLUDE = pkgkeys[1] | |
46 | ||
ea1f7807 | 47 | sourcefiles = [] |
58e0ac49 | 48 | pkg = getLibPackage(lib) |
49 | einclude = [ "%s/include" % rootsys, "%s/STEER" % alice_root, "%s/%s" % (alice_root,pkg) ] | |
50 | ||
51 | dir = os.path.dirname(lib) | |
52 | ||
53 | try: | |
54 | f = open(lib) | |
55 | except: | |
56 | print "getSourceFiles : could not open package file %s" % lib | |
57 | return sourcefiles, einclude | |
58 | ||
59 | src = False | |
60 | ||
ea1f7807 | 61 | for line in f: |
62 | l = line.strip() | |
58e0ac49 | 63 | key = False |
64 | for k in pkgkeys: | |
65 | if re.search(k,l): | |
66 | key = True | |
67 | if key: | |
68 | if re.search("^%s" % keySRCS,l): | |
69 | src = True | |
70 | else: | |
71 | src = False | |
72 | if re.search("^%s" % keyEINCLUDE,l): | |
73 | l = re.sub(keyEINCLUDE,' ',l) | |
74 | l = re.sub(':',' ',l) | |
75 | l = re.sub('=',' ',l) | |
76 | l = re.sub('\+',' ',l) | |
77 | a = l.split() | |
78 | for i in a: | |
79 | append(einclude,os.path.join(alice_root,i)) | |
80 | ||
81 | if src: | |
82 | if re.search('Ali',l) and ( re.search('.cxx',l) or re.search('.h',l) ): | |
83 | l = re.sub(keySRCS,' ',l) | |
84 | l = re.sub(':',' ',l) | |
85 | l = re.sub('=',' ',l) | |
86 | l = re.sub('\+',' ',l) | |
87 | l = re.sub("\\\\",' ',l) | |
88 | for i in l.split(): | |
89 | append(sourcefiles,os.path.join(dir,i)) | |
90 | ||
ea1f7807 | 91 | f.close() |
58e0ac49 | 92 | return sourcefiles,einclude |
ea1f7807 | 93 | |
94 | #_______________________________________________________________________________ | |
58e0ac49 | 95 | def getIncludeFiles2(srcfile,alice_root,alice_target,rootsys): |
96 | """Extract the list of included classes from a class, using the dep | |
97 | files generated in $ALICE_ROOT/package/tgt_ALICE_TARGET/*.d files | |
98 | It is much faster than getIncludeFile, as it reuses the output of | |
99 | previously preprocessing part. Drawback is that it will only work | |
100 | on a compiled version of aliroot... | |
ea1f7807 | 101 | """ |
102 | ||
103 | includes = [] | |
104 | ||
58e0ac49 | 105 | package = getFilePackage(srcfile,alice_root) |
106 | file = re.sub("%s/%s" % (alice_root,package)," ",srcfile).strip() | |
107 | if file[0] == '/': | |
108 | file = file[1:] | |
109 | depfile = "%s/%s/tgt_%s/%s" % (alice_root,package,alice_target,file) | |
110 | depfile = re.sub("\.cxx",".d",depfile) | |
111 | ||
ea1f7807 | 112 | try: |
58e0ac49 | 113 | f = open(depfile) |
ea1f7807 | 114 | except: |
58e0ac49 | 115 | print "Could not open file %s" % depfile |
116 | print "From",srcfile | |
ea1f7807 | 117 | return includes |
118 | ||
119 | for line in f: | |
120 | line = line.strip() | |
58e0ac49 | 121 | i = line.find(":") |
122 | if i > 0: | |
123 | line = line[i+1:] | |
124 | parts = line.strip().split() | |
125 | for p in parts: | |
126 | if re.search(rootsys,p): | |
127 | p = rootsys | |
128 | else: | |
129 | if p[0] != '/': | |
130 | p = "%s/%s" % (alice_root,p) | |
131 | p = re.sub("%s/include" % alice_root,"%s/STEER" % alice_root,p) | |
132 | append(includes,p) | |
133 | ||
ea1f7807 | 134 | f.close() |
58e0ac49 | 135 | |
136 | return includes | |
137 | ||
138 | #_______________________________________________________________________________ | |
139 | def getIncludeFiles(srcfile,eincludes,rootsys): | |
140 | """Extract the list of included classes from a class, using : | |
141 | gcc -MM srcfile -MG | |
142 | and then parses the output... | |
143 | This version is quite slow as we're (re-)doing the preprocessing of | |
144 | all the files, but the advantage is that it'll work for a fresh checkout | |
145 | of aliroot, i.e. even before compilation | |
146 | """ | |
147 | ||
148 | includes = [] | |
149 | ||
150 | # try: | |
151 | # f = open(srcfile) | |
152 | # except: | |
153 | # print "Could not open file %s" % srcfile | |
154 | # return includes | |
155 | # | |
156 | # f.close() | |
157 | ||
158 | ||
159 | incdir = "" | |
160 | ||
161 | for i in eincludes: | |
162 | incdir = "%s -I%s" % (incdir,i) | |
163 | ||
164 | cmd = "gcc %s -MM %s -MG" % (incdir,srcfile) | |
165 | ||
166 | pre = os.popen(cmd) | |
167 | ||
168 | for line in pre: | |
169 | line = line.strip() | |
170 | line = re.sub("\\\\"," ",line) | |
171 | i = line.find(":") | |
172 | if i > 0: | |
173 | line = line[i+1:] | |
174 | line = line.strip() | |
175 | if len(line) > 0 and line != srcfile: | |
176 | if line.find('/') < 0: | |
177 | print "Got no path for file",srcfile," line=",line | |
178 | print "cmd was",cmd | |
179 | if re.search(rootsys,line): | |
180 | line = rootsys | |
181 | append(includes,line) | |
182 | pre.close() | |
183 | ||
ea1f7807 | 184 | return includes |
185 | ||
186 | #_______________________________________________________________________________ | |
187 | def unique(list): | |
188 | """Extract a unique list from list | |
189 | """ | |
190 | d = {} | |
191 | for l in list: | |
192 | d[l] = 1 | |
193 | return d.keys() | |
194 | ||
195 | #_______________________________________________________________________________ | |
58e0ac49 | 196 | def libshorten(libname): |
197 | """From libYYYxxx.pkg to YYYxxx | |
ea1f7807 | 198 | """ |
ea1f7807 | 199 | |
58e0ac49 | 200 | s = os.path.basename(libname) |
201 | if re.search("^lib",s): | |
202 | s = re.sub("^lib","",s) | |
203 | s = re.sub("\.pkg","",s) | |
204 | ||
205 | return s | |
206 | ||
ea1f7807 | 207 | #_______________________________________________________________________________ |
58e0ac49 | 208 | def fileshorten(file,path): |
209 | """From path/toto/file to toto/file | |
ea1f7807 | 210 | """ |
58e0ac49 | 211 | |
212 | s = re.sub(path," ",file).strip() | |
213 | if s[0] == '/': | |
214 | s = s[1:] | |
215 | ||
ea1f7807 | 216 | return s |
58e0ac49 | 217 | |
218 | #_______________________________________________________________________________ | |
219 | def getFilePackage(file,alice_root): | |
220 | """ Get the package in which this file is defined | |
221 | """ | |
222 | ||
223 | f = re.sub(alice_root,"/",file) | |
224 | while f[0] == '/': | |
225 | f = f[1:] | |
226 | p = f.split('/') | |
227 | return p[0] | |
228 | ||
229 | #_______________________________________________________________________________ | |
230 | def getLibPackage(libname): | |
231 | """ Get the package in which this library is defined | |
232 | """ | |
233 | ||
234 | p = libname.split('/') | |
235 | return p[len(p)-2] | |
236 | ||
237 | #_______________________________________________________________________________ | |
238 | def tryRecover(f,inc2src,src2lib,alice_root): | |
239 | """ This method should try to recover the "father" of file f (most probably | |
240 | f is an include file | |
241 | The idea would be to find a cxx file that *directly* includes f, and take | |
242 | the lib of that cxx file as the source of f... | |
243 | Would that work ? | |
244 | Is it needed really ? | |
245 | """ | |
246 | ||
247 | """ | |
248 | print "tryRecover:",f | |
249 | ||
250 | if not f.find('\.h'): | |
251 | return "" | |
252 | ||
253 | p = getFilePackage(f,alice_root) | |
254 | ||
255 | cxxfiles = inc2src.get(f,[]) | |
256 | ||
257 | for file in cxxfiles: | |
258 | libs = src2lib.get(file,[]) | |
259 | ||
260 | for l in libs: | |
261 | pl = getLibPackage(l) | |
262 | print f,file,p,pl | |
263 | """ | |
264 | ||
265 | return "" | |
ea1f7807 | 266 | |
267 | #_______________________________________________________________________________ | |
268 | #_______________________________________________________________________________ | |
269 | #_______________________________________________________________________________ | |
270 | def main(): | |
271 | ||
58e0ac49 | 272 | # we cannot work w/o those environement variables, so check them... |
273 | requiredVariables = [ "ROOTSYS", "ALICE_ROOT", "ALICE_TARGET" ] | |
274 | ||
275 | for r in requiredVariables: | |
276 | if not r in os.environ: | |
277 | print "%s is not defined. Cannot work." % r | |
278 | sys.exit(1) | |
279 | ||
280 | alice_root = os.environ.get("ALICE_ROOT") | |
281 | rootsys = os.environ.get("ROOTSYS") | |
282 | alice_target = os.environ.get("ALICE_TARGET") | |
283 | ||
284 | debug = 0 | |
285 | noda = True | |
ea1f7807 | 286 | |
287 | try: | |
58e0ac49 | 288 | opts, args = getopt.getopt(sys.argv[1:],"hd",["help", "debug","da"]) |
ea1f7807 | 289 | except getopt.GetoptError: |
290 | print "Error in options" | |
291 | usage() | |
292 | ||
293 | for o, a in opts: | |
294 | if o in ( "-d","--debug" ): | |
58e0ac49 | 295 | debug = debug + 1 |
ea1f7807 | 296 | elif o in ( "-h","--help" ): |
297 | usage() | |
298 | sys.exit() | |
58e0ac49 | 299 | elif o == "--da": |
300 | noda = False | |
ea1f7807 | 301 | else: |
302 | assert False, "unhandled option" | |
303 | ||
58e0ac49 | 304 | dir = os.path.abspath(args[0]) |
305 | dirs = [] | |
306 | ||
307 | for sd in os.listdir(dir): | |
308 | ld = os.path.join(dir,sd) | |
309 | if os.path.isdir(ld) and not os.path.islink(ld): | |
310 | dirs.append(ld) | |
311 | ||
312 | dirs.append(dir) | |
313 | ||
314 | requestedPackages = [ "MUON", "STEER", "RAW", "ITS", "TRD", "VZERO", "TPC", "PHOS", "TOF", "ZDC", "EMCAL", "HMPID", "SHUTTLE", "ACORDE" ]; | |
ea1f7807 | 315 | |
316 | # find the libraries defined in this directory (looking for libXXXX.pkg files) | |
317 | libraries = [] | |
318 | ||
58e0ac49 | 319 | for d in dirs: |
320 | for f in os.listdir(d): | |
321 | fulllib = os.path.join(d,f) | |
322 | p = getLibPackage(fulllib) | |
323 | if not p in requestedPackages: | |
324 | continue | |
325 | if re.search('^lib',f) and re.search('.pkg$',f): | |
326 | libraries.append(fulllib) | |
327 | if not noda and re.search('da.cxx',f) and not re.search('.svn',f): | |
328 | # append fake libraries for DAs | |
329 | tmp = re.sub("cxx","pkg",f) | |
330 | tmp = "lib%s" % tmp | |
331 | libraries.append(os.path.join(d,tmp)) | |
332 | ||
333 | # from list of library files (libXXXyyy.pkg), try to find back the list of | |
334 | # "packages" = XXX | |
335 | packages = {} | |
336 | ||
337 | for l in libraries: | |
338 | p = getLibPackage(l) | |
339 | packages[p] = [] | |
340 | ||
341 | for l in libraries: | |
342 | p = getLibPackage(l) | |
343 | packages[p].append(l) | |
344 | ||
345 | # src2inc[file.cxx] -> { all included files of that file } | |
346 | src2inc = {} | |
347 | ||
348 | # inc2src[file.h] -> { all files that include that one } | |
349 | inc2src = {} | |
350 | ||
351 | # lib2src[libXXX.pkg] -> { list of files of that library } | |
352 | lib2src = {} | |
353 | ||
354 | # src2lib[file.cxx] -> { list of libraries including that file } | |
355 | src2lib = {} | |
356 | ||
357 | # eincludes[libXXX.pkg] -> list of directories to be included to be able to compile the files | |
358 | eincludes = {} | |
359 | ||
360 | # lib2inc[libXXX.pkg] -> { list of all included files of that library } | |
361 | lib2inc = {} | |
362 | ||
363 | # inc2lib[file.h] -> { list of libraries that include that file } | |
364 | inc2lib = {} | |
365 | ||
366 | for p in packages: | |
367 | ||
368 | print "Scanning ",p | |
369 | ||
370 | for lib in packages[p]: | |
371 | ||
372 | lib2inc[lib] = [] | |
373 | ||
374 | print " ",libshorten(lib),"..." | |
375 | ||
376 | if not re.search("da.pkg",lib): | |
377 | # handle the special case of DAs which are not part of libs, really | |
378 | lib2src[lib], eincludes[lib] = getSourceFiles(lib,rootsys,alice_root) | |
379 | else: | |
380 | l = lib | |
381 | l = re.sub("lib","",l) | |
382 | l = re.sub("\.pkg","",l) | |
383 | lib2src[lib] = [ "%s.cxx" % l ] | |
384 | eincludes[lib] = [] | |
385 | ||
386 | files = [] | |
ea1f7807 | 387 | |
58e0ac49 | 388 | for src in lib2src[lib]: |
389 | # inc = getIncludeFiles(src,eincludes[lib],rootsys) | |
390 | inc = getIncludeFiles2(src,alice_root,alice_target,rootsys) | |
391 | src2inc[src] = inc | |
392 | ||
393 | if not src in src2lib: | |
394 | src2lib[src] = [] | |
395 | ||
396 | append(src2lib[src],lib) | |
397 | ||
398 | for i in inc: | |
399 | if not i in inc2src.keys(): | |
400 | inc2src[i] = [] | |
401 | append(inc2src[i],src) | |
402 | append(lib2inc[lib],i) | |
403 | if not i in inc2lib.keys(): | |
404 | inc2lib[i] = [] | |
405 | append(inc2lib[i],lib) | |
406 | ||
407 | # some debug at this point... | |
ea1f7807 | 408 | |
58e0ac49 | 409 | if debug>=1: |
410 | for lib in libraries: | |
411 | print lib," is made of " | |
412 | for f in lib2src[lib]: | |
413 | print " ",fileshorten(f,alice_root) | |
414 | print " and includes " | |
415 | for h in lib2inc[lib]: | |
416 | print " ",fileshorten(h,alice_root) | |
417 | if len(eincludes[lib]) > 0: | |
418 | print " and needs the following directories to be compiled " | |
419 | for f in eincludes[lib]: | |
420 | print " ",f | |
421 | ||
422 | if debug>=2: | |
423 | print "src2lib relationship" | |
424 | for src,lib in src2lib.items(): | |
425 | print fileshorten(src,alice_root),"(", | |
426 | for l in lib: | |
427 | print libshorten(l)," ", | |
428 | print ")" | |
429 | ||
430 | # now fills the ultimate array, lib2lib | |
431 | # lib2lib[libXXX.pkg] -> { libYYY.pkg }, list of libraries that lib depends on | |
ea1f7807 | 432 | |
58e0ac49 | 433 | lib2lib = {} |
ea1f7807 | 434 | |
435 | for lib in libraries: | |
58e0ac49 | 436 | |
437 | lib2lib[lib] = [] | |
438 | ||
439 | for hfile in lib2inc[lib]: | |
440 | ||
441 | l = "external" | |
442 | ||
443 | # start simple : is f contains ROOTSYS, it's ROOT. | |
444 | if re.search(rootsys,hfile): | |
445 | l = "ROOT" | |
446 | else: | |
447 | # not that simple, let's try to find out... | |
448 | cxx = re.sub("\.h",".cxx",hfile) | |
449 | dl = src2lib.get(cxx,[]) | |
450 | if len(dl)==1: | |
451 | l = dl[0] | |
452 | elif len(dl)>1: | |
453 | print "Got several libs(",len(dl),"for ",hfile,":" | |
454 | print dl | |
455 | ||
456 | if l =="external": | |
457 | notassociatedfiles[hfile] = 1 | |
458 | else: | |
459 | append(lib2lib[lib],l) | |
460 | ||
461 | ###################### Debug parts... | |
462 | if debug>=1: | |
ea1f7807 | 463 | for lib in libraries: |
58e0ac49 | 464 | print libshorten(lib),"depends on" |
465 | for f in lib2lib[lib]: | |
466 | print " ",libshorten(f) | |
467 | ||
468 | if debug>=2: | |
469 | ||
470 | print "From source files to include files : " | |
471 | for cxxfile, hfile in src2inc.items(): | |
472 | print fileshorten(cxxfile,alice_root) | |
473 | for h in hfile: | |
474 | print " ",fileshorten(h,alice_root) | |
475 | ||
476 | if debug>=3: | |
ea1f7807 | 477 | |
58e0ac49 | 478 | print "From include files to source files : " |
479 | for i,sources in inc2src.items(): | |
480 | print fileshorten(i,alice_root), len(sources) | |
481 | for s in sources: | |
482 | print " ",fileshorten(s,alice_root),"(", | |
483 | for l in src2lib[s]: | |
484 | print libshorten(l), | |
485 | print ")" | |
486 | ###################### | |
487 | ||
488 | if len(notassociatedfiles) > 0: | |
489 | print "The following files could not be associated with any library..." | |
490 | for f in notassociatedfiles.keys(): | |
491 | print f, | |
492 | t=tryRecover(f,inc2src,src2lib,alice_root) | |
493 | print t | |
ea1f7807 | 494 | |
58e0ac49 | 495 | # output the dot file that will have to be processed by the dot program |
496 | ||
ea1f7807 | 497 | ofile = "%s.dot" % os.path.splitext(os.path.basename(sys.argv[0]))[0] |
498 | ||
499 | f = open(ofile,"w") | |
500 | ||
58e0ac49 | 501 | f.write("digraph G {\n") |
502 | f.write("rankdir=BT;\n") | |
ea1f7807 | 503 | |
58e0ac49 | 504 | defaultcolor = "lightblue" |
505 | ||
506 | colors = {} | |
507 | ||
508 | colors["MUON"] = "lightyellow" | |
509 | colors["STEER"] = "lightgray" | |
510 | ||
511 | for l,d in lib2lib.items(): | |
ea1f7807 | 512 | for dl in d: |
58e0ac49 | 513 | f.write("%s->%s;\n" %(libshorten(l),libshorten(dl))) |
ea1f7807 | 514 | |
58e0ac49 | 515 | for p in packages: |
516 | f.write("subgraph cluster_%s {\n" % p.lower()) | |
517 | color = colors.get(p,defaultcolor) | |
518 | f.write("style=filled;\n") | |
519 | f.write("color=%s;\n" % color) | |
520 | f.write('label="%s";\n' % p) | |
521 | for lib in packages[p]: | |
522 | f.write("%s\n" % libshorten(lib)) | |
523 | f.write("}\n") | |
524 | ||
525 | f.write("}\n") | |
ea1f7807 | 526 | |
527 | print "You should now do :" | |
528 | print "tred %s > %s.bis" % ( ofile, ofile ) | |
58e0ac49 | 529 | print "dot -Tpng %s.bis -o %s.png" % (ofile,ofile) |
ea1f7807 | 530 | |
531 | if __name__ == "__main__": | |
532 | main() | |
533 |