__author__ = "L. Aphecetche aphecetc_at_in2p3_dot_fr"
__version__ = "$Id$"
+notassociatedfiles = {}
+
#_______________________________________________________________________________
def usage():
"""Describe usage of script
"""
- print "Usage: %s [-h | --help] [-d | --debug] [--noda] directory_to_scan" % sys.argv[0]
+ print "Usage: %s [-h | --help] [-d | --debug] [--da] directory_to_scan" % sys.argv[0]
sys.exit(1)
#_______________________________________________________________________________
-def getSourceFiles(lib):
- """Extract the list of classes from a libXXX.pkg file
+def append(list,a):
+ """ append a to list, if a not there yet
+ """
+ if not a in list:
+ list.append(a)
+
+#_______________________________________________________________________________
+def isempty(line):
+
+ return len(line) < 2
+
+#_______________________________________________________________________________
+def iscomment(line):
+
+ return re.search("^#",line)
+
+#_______________________________________________________________________________
+def iscontinuation(line):
+
+ return line.find('\\') > 0
+
+#_______________________________________________________________________________
+def compactLines(ilines):
+ """ Given an array of lines, remove empty lines, comment lines
+ and concatenate lines that should be one (i.e. removing the \ continuation
+ marks...
"""
+
+ continuation = False
- f = open(lib)
+ olines = []
+
+ currentline = ""
+
+ i = 0
+
+ for line in ilines:
+
+ i = i + 1
+
+ if line.find('\\') > 0:
+ line = re.sub("\t|\n|\r|\\\\","",line)
+ continuation = True
+ else:
+ continuation = False
+ if isempty(currentline):
+ currentline = line
+ else:
+ l = re.sub("\t","",line)
+ currentline = currentline + re.sub(" {1,}"," ",l)
+ if not iscomment(currentline) and not isempty(currentline):
+ olines.append(re.sub(" {1,}"," ",currentline))
+ currentline = ""
+
+ if continuation:
+ currentline = currentline + line
+
+ return olines
+
+#_______________________________________________________________________________
+def tokenizeLines(lines):
+ """
+ Return a dict of keys -> value items.
+ keys are the left part of lines supposed to be of the form :
+ KEY:=VALUE
+ or
+ KEY+=VALUE
+ or
+ KEY=VALUE
+ """
+
+ tokens = {}
+
+ define = ":="
+ plus = "+="
+ equal = "="
+
+ separators = [ define, plus, equal ]
+
+ for l in lines:
+ sep = False
+ for s in separators:
+ if s in l:
+ a = l.split(s,1)
+ key = a[0].strip()
+ value = re.sub("\n|\t|\n","",a[1])
+ if len(a) > 2:
+ print "Something fishy here !"
+ sep = True
+ break
+ if not sep:
+ continue
+ if not key in tokens.keys():
+ tokens[key] = ""
+ if s == plus:
+ tokens[key] += value
+ else:
+ tokens[key] = value
+
+ return tokens
+
+#_______________________________________________________________________________
+def variableSubstitution(tokenname,alltokens):
+ """
+ """
+
+ value = alltokens.get(tokenname,"")
+
+ for k in alltokens.keys():
+ if re.search("\$\(%s\)"%k,value):
+ # found something like $(VARIABLE), so expand that variable,
+ # by calling us again
+ rep = value.replace("$(%s)"%k,variableSubstitution(k,alltokens))
+ return rep
+ if re.search("\$\(%s\:"%k,value):
+ # found something like $(VARIABLE:
+ # we suppose it's then something like $(VARIABLE:.x=.y)
+ # i.e. we replace x by y in VARIABLE's expansion
+ t = variableSubstitution(k,alltokens)
+ i1 = value.index(":")
+ i2 = value.index(")")
+ change = value[i1+1:i2].split("=")
+ return t.replace(change[0],change[1])
+
+ return value
+
+#_______________________________________________________________________________
+def patternSubstitution(value):
+
+ if re.search("\$\(patsubst",value):
+ rv = []
+ # found the Makefile function $(patsubst %.x, %.y, list)
+ i1 = value.index("(")
+ i2 = value.rindex(")")
+ a = value[i1+1:i2].split(",")
+ source = a[0].replace("patsubst ","")
+ destination = a[1].replace("%","")
+ if source != "%":
+ print "Houston, we have a problem : ",value
+ sys.exit(1)
+ for l in a[2].split():
+ rv.append(destination + l)
+ return rv
+
+ return value.split()
+
+#_______________________________________________________________________________
+def getSourceFiles2(lib,rootsys,alice_root):
+ """Extract the list of files from a libXXX.pkg file
+ Return a pair of list (sourceFiles,einclude), where einclude
+ is the list of directories needed to be included compile the files.
+ """
+
+ try:
+ f = open(lib)
+ except:
+ print "Cannot open file ", file
+ sys.exit(1)
+
+ filelines = f.readlines()
+
+ f.close()
+
+ lines = compactLines(filelines)
+
+ tokens = tokenizeLines(lines)
+
+ sourcesfiles = patternSubstitution(variableSubstitution("SRCS",tokens))
+ eincludes = patternSubstitution(variableSubstitution("EINCLUDE",tokens))
+
+ pkg = getLibPackage(lib)
+ dir = os.path.join(alice_root,pkg)
+
+ sourcesfiles = [ os.path.join(dir,x) for x in sourcesfiles ]
+
+ return sourcesfiles,eincludes
+
+#_______________________________________________________________________________
+def getSourceFiles(lib,rootsys,alice_root):
+ """Extract the list of files from a libXXX.pkg file
+ Return a pair of list (sourceFiles,einclude), where einclude
+ is the list of directories needed to be included compile the files.
+ """
+
+ # list of possible .pkg variables
+ pkgkeys = [ "SRCS","EINCLUDE","HDRS","FSRCS","DHDR","CSRCS","CHDRS","ELIBS","EDEFINE","PACKFFLAGS","PACKCXXFLAGS","PACKCFLAGS","PACKSOFLAGS","EXPORT","EHDRS" ]
+
+ keySRCS = pkgkeys[0]
+ keyEINCLUDE = pkgkeys[1]
+
sourcefiles = []
+ pkg = getLibPackage(lib)
+ einclude = [ "%s/include" % rootsys, "%s/STEER" % alice_root, "%s/%s" % (alice_root,pkg) ]
+
+ dir = os.path.dirname(lib)
+
+ try:
+ f = open(lib)
+ except:
+ print "getSourceFiles : could not open package file %s" % lib
+ return sourcefiles, einclude
+
+ src = False
+
for line in f:
l = line.strip()
- if re.search('Ali',l) and re.search('.cxx',l):
- l = re.sub('SRCS',' ',l)
- l = re.sub(':=',' ',l)
- l = re.sub('=',' ',l)
- l = re.sub("\\\\",' ',l)
- l = re.sub(".cxx",' ',l)
- for i in l.split():
- sourcefiles.append(i)
+ key = False
+ for k in pkgkeys:
+ if re.search(k,l):
+ key = True
+ if key:
+ if re.search("^%s" % keySRCS,l):
+ src = True
+ else:
+ src = False
+ if re.search("^%s" % keyEINCLUDE,l):
+ l = re.sub(keyEINCLUDE,' ',l)
+ l = re.sub(':',' ',l)
+ l = re.sub('=',' ',l)
+ l = re.sub('\+',' ',l)
+ a = l.split()
+ for i in a:
+ append(einclude,os.path.join(alice_root,i))
+
+ if src:
+ if re.search('Ali',l) and ( re.search('.cxx',l) or re.search('.h',l) ):
+ l = re.sub(keySRCS,' ',l)
+ l = re.sub(':',' ',l)
+ l = re.sub('=',' ',l)
+ l = re.sub('\+',' ',l)
+ l = re.sub("\\\\",' ',l)
+ for i in l.split():
+ append(sourcefiles,os.path.join(dir,i))
+
f.close()
- return sourcefiles
+ return sourcefiles,einclude
#_______________________________________________________________________________
-def getIncludeFiles(srcfile):
- """Extract the list of included classes from a class
+def getIncludeFiles2(srcfile,alice_root,alice_target,rootsys):
+ """Extract the list of included classes from a class, using the dep
+ files generated in $ALICE_ROOT/package/tgt_ALICE_TARGET/*.d files
+ It is much faster than getIncludeFile, as it reuses the output of
+ previously preprocessing part. Drawback is that it will only work
+ on a compiled version of aliroot...
"""
includes = []
+ package = getFilePackage(srcfile,alice_root)
+ file = re.sub("%s/%s" % (alice_root,package)," ",srcfile).strip()
+ if file[0] == '/':
+ file = file[1:]
+ depfile = "%s/%s/tgt_%s/%s" % (alice_root,package,alice_target,file)
+ depfile = re.sub("\.cxx",".d",depfile)
+
try:
- f = open("%s.cxx" % srcfile)
+ f = open(depfile)
except:
- print "Could not open file %s.cxx" % srcfile
+ print "Could not open file %s" % depfile
+ print "From",srcfile
return includes
for line in f:
line = line.strip()
- if re.search("^#",line) and re.search("#include",line) and re.search('Ali',line):
- line = re.sub("#include",' ',line)
- i = line.index(".h")
- line = line[:i]
- line = re.sub("\"",' ',line)
- line = line.strip()
- includes.append(line)
+ i = line.find(":")
+ if i > 0:
+ line = line[i+1:]
+ parts = line.strip().split()
+ for p in parts:
+ if re.search(rootsys,p):
+ p = rootsys
+ else:
+ if p[0] != '/':
+ p = "%s/%s" % (alice_root,p)
+ p = re.sub("%s/include" % alice_root,"%s/STEER" % alice_root,p)
+ append(includes,p)
+
f.close()
+
+ return includes
+
+#_______________________________________________________________________________
+def getIncludeFiles(srcfile,eincludes,rootsys):
+ """Extract the list of included classes from a class, using :
+ gcc -MM srcfile -MG
+ and then parses the output...
+ This version is quite slow as we're (re-)doing the preprocessing of
+ all the files, but the advantage is that it'll work for a fresh checkout
+ of aliroot, i.e. even before compilation
+ """
+
+ includes = []
+
+# try:
+# f = open(srcfile)
+# except:
+# print "Could not open file %s" % srcfile
+# return includes
+#
+# f.close()
+
+
+ incdir = ""
+
+ for i in eincludes:
+ incdir = "%s -I%s" % (incdir,i)
+
+ cmd = "gcc %s -MM %s -MG" % (incdir,srcfile)
+
+ pre = os.popen(cmd)
+
+ for line in pre:
+ line = line.strip()
+ line = re.sub("\\\\"," ",line)
+ i = line.find(":")
+ if i > 0:
+ line = line[i+1:]
+ line = line.strip()
+ if len(line) > 0 and line != srcfile:
+ if line.find('/') < 0:
+ print "Got no path for file",srcfile," line=",line
+ print "cmd was",cmd
+ if re.search(rootsys,line):
+ line = rootsys
+ append(includes,line)
+ pre.close()
+
return includes
#_______________________________________________________________________________
return d.keys()
#_______________________________________________________________________________
-def findLibrary(file,allfiles):
- """Find in which library a given class is defined
+def libshorten(libname):
+ """From libYYYxxx.pkg to YYYxxx
"""
- for k,v in allfiles.items():
- for f in v:
- a,f = os.path.split(f)
- if file == f:
- return k
- return "external"
+ s = os.path.basename(libname)
+ if re.search("^lib",s):
+ s = re.sub("^lib","",s)
+ s = re.sub("\.pkg","",s)
+
+ return s
+
#_______________________________________________________________________________
-def shorten(libname):
- """From libYYYxxx.pkg to YYYxxx
+def fileshorten(file,path):
+ """From path/toto/file to toto/file
"""
- s = libname
- if re.search("lib",libname):
- s = re.sub("lib","",s)
- s = re.sub("\.pkg","",s)
+
+ s = re.sub(path," ",file).strip()
+ if s[0] == '/':
+ s = s[1:]
+
return s
+
+#_______________________________________________________________________________
+def getFilePackage(file,alice_root):
+ """ Get the package in which this file is defined
+ """
+
+ f = re.sub(alice_root,"/",file)
+ while f[0] == '/':
+ f = f[1:]
+ p = f.split('/')
+ return p[0]
+
+#_______________________________________________________________________________
+def getLibPackage(libname):
+ """ Get the package in which this library is defined
+ """
+
+ p = libname.split('/')
+ return p[len(p)-2]
+
+#_______________________________________________________________________________
+def tryRecover(f,inc2src,src2lib,alice_root):
+ """ This method should try to recover the "father" of file f (most probably
+ f is an include file
+ The idea would be to find a cxx file that *directly* includes f, and take
+ the lib of that cxx file as the source of f...
+ Would that work ?
+ Is it needed really ?
+ """
+
+ """
+ print "tryRecover:",f
+
+ if not f.find('\.h'):
+ return ""
+
+ p = getFilePackage(f,alice_root)
+
+ cxxfiles = inc2src.get(f,[])
+
+ for file in cxxfiles:
+ libs = src2lib.get(file,[])
+
+ for l in libs:
+ pl = getLibPackage(l)
+ print f,file,p,pl
+ """
+
+ return ""
#_______________________________________________________________________________
#_______________________________________________________________________________
#_______________________________________________________________________________
def main():
- debug = False
- noda = False
+ # we cannot work w/o those environement variables, so check them...
+ requiredVariables = [ "ROOTSYS", "ALICE_ROOT", "ALICE_TARGET" ]
+
+ for r in requiredVariables:
+ if not r in os.environ:
+ print "%s is not defined. Cannot work." % r
+ sys.exit(1)
+
+ alice_root = os.environ.get("ALICE_ROOT")
+ rootsys = os.environ.get("ROOTSYS")
+ alice_target = os.environ.get("ALICE_TARGET")
+
+ debug = 0
+ noda = True
try:
- opts, args = getopt.getopt(sys.argv[1:],"hd",["help", "debug","noda"])
+ opts, args = getopt.getopt(sys.argv[1:],"hd",["help", "debug","da"])
except getopt.GetoptError:
print "Error in options"
usage()
for o, a in opts:
if o in ( "-d","--debug" ):
- debug = True
+ debug = debug + 1
elif o in ( "-h","--help" ):
usage()
sys.exit()
- elif o == "--noda":
- noda = True
+ elif o == "--da":
+ noda = False
else:
assert False, "unhandled option"
- dir = args[0]
+ dir = os.path.abspath(args[0])
+ dirs = []
+
+ for sd in os.listdir(dir):
+ ld = os.path.join(dir,sd)
+ if os.path.isdir(ld) and not os.path.islink(ld):
+ dirs.append(ld)
+
+ dirs.append(dir)
+
+# requestedPackages = [ "MUON", "STEER", "RAW", "ITS", "TRD", "VZERO", "TPC", "PHOS", "TOF", "ZDC", "EMCAL", "HMPID", "SHUTTLE", "ACORDE", "HLT", "EVE" ];
+ requestedPackages = [ "RAW", "STEER", "MUON", "HLT","EVE" ]
# find the libraries defined in this directory (looking for libXXXX.pkg files)
libraries = []
- for file in os.listdir(dir):
- if re.search('^lib',file) and re.search('.pkg$',file):
- libraries.append(file)
+ for d in dirs:
+ for f in os.listdir(d):
+ fulllib = os.path.join(d,f)
+ p = getLibPackage(fulllib)
+ if not p in requestedPackages:
+ continue
+ if re.search('^lib',f) and re.search('.pkg$',f):
+ libraries.append(fulllib)
+ if not noda and re.search('da.cxx',f) and not re.search('.svn',f):
+ # append fake libraries for DAs
+ tmp = re.sub("cxx","pkg",f)
+ tmp = "lib%s" % tmp
+ libraries.append(os.path.join(d,tmp))
+
+ # from list of library files (libXXXyyy.pkg), try to find back the list of
+ # "packages" = XXX
+ packages = {}
+
+ for l in libraries:
+ p = getLibPackage(l)
+ packages[p] = []
+
+ for l in libraries:
+ p = getLibPackage(l)
+ packages[p].append(l)
+
+ # src2inc[file.cxx] -> { all included files of that file }
+ src2inc = {}
+
+ # inc2src[file.h] -> { all files that include that one }
+ inc2src = {}
+
+ # lib2src[libXXX.pkg] -> { list of files of that library }
+ lib2src = {}
- # append fake libraries for DAs
- if not noda:
- libraries.append("libMUONTRKda.pkg")
- libraries.append("libMUONTRGda.pkg")
+ # src2lib[file.cxx] -> { list of libraries including that file }
+ src2lib = {}
+
+ # eincludes[libXXX.pkg] -> list of directories to be included to be able to compile the files
+ eincludes = {}
- # srcfiles is a dictionary :
- # srcfiles[libXXX.pkg] -> { list of classes (inferred from list of .cxx files) }
- #
- srcfiles = {}
+ # lib2inc[libXXX.pkg] -> { list of all included files of that library }
+ lib2inc = {}
+
+ # inc2lib[file.h] -> { list of libraries that include that file }
+ inc2lib = {}
+
+ for p in packages:
+
+ print "Scanning ",p
+
+ for lib in packages[p]:
+
+ lib2inc[lib] = []
+
+ print " ",libshorten(lib),"..."
+
+ if not re.search("da.pkg",lib):
+ # handle the special case of DAs which are not part of libs, really
+ lib2src[lib], eincludes[lib] = getSourceFiles2(lib,rootsys,alice_root)
+ else:
+ l = lib
+ l = re.sub("lib","",l)
+ l = re.sub("\.pkg","",l)
+ lib2src[lib] = [ "%s.cxx" % l ]
+ eincludes[lib] = []
+
+ files = []
+
+ for src in lib2src[lib]:
+# inc = getIncludeFiles(src,eincludes[lib],rootsys)
+ inc = getIncludeFiles2(src,alice_root,alice_target,rootsys)
+ src2inc[src] = inc
+
+ if not src in src2lib:
+ src2lib[src] = []
+
+ append(src2lib[src],lib)
+
+ for i in inc:
+ if not i in inc2src.keys():
+ inc2src[i] = []
+ append(inc2src[i],src)
+ append(lib2inc[lib],i)
+ if not i in inc2lib.keys():
+ inc2lib[i] = []
+ append(inc2lib[i],lib)
+
+ # some debug at this point...
+
+ if debug>=1:
+ for lib in libraries:
+ print lib," is made of "
+ for f in lib2src[lib]:
+ print " ",fileshorten(f,alice_root)
+ print " and includes "
+ for h in lib2inc[lib]:
+ print " ",fileshorten(h,alice_root)
+ if len(eincludes[lib]) > 0:
+ print " and needs the following directories to be compiled "
+ for f in eincludes[lib]:
+ print " ",f
+
+ if debug>=2:
+ print "src2lib relationship"
+ for src,lib in src2lib.items():
+ print fileshorten(src,alice_root),"(",
+ for l in lib:
+ print libshorten(l)," ",
+ print ")"
+
+ # now fills the ultimate array, lib2lib
+ # lib2lib[libXXX.pkg] -> { libYYY.pkg }, list of libraries that lib depends on
- # allfiles is a dictonary :
- # allfiles[libXXX.pkg] -> { list of all included files of that library }
- allfiles = {}
+ lib2lib = {}
for lib in libraries:
- if not re.search("da",lib):
- # handle the special case of DAs which are not part of libs, really
- srcfiles[lib] = getSourceFiles(lib)
- else:
- l = lib
- l = re.sub("lib","",l)
- l = re.sub("\.pkg","",l)
- srcfiles[lib] = [ l ]
- files = []
- for src in srcfiles[lib]:
- files.extend(getIncludeFiles(src))
- allfiles[lib] = unique(files)
-
- if debug:
+
+ lib2lib[lib] = []
+
+ for hfile in lib2inc[lib]:
+
+ l = "external"
+
+ # start simple : is f contains ROOTSYS, it's ROOT.
+ if re.search(rootsys,hfile):
+ l = "ROOT"
+ else:
+ # not that simple, let's try to find out...
+ cxx = re.sub("\.h",".cxx",hfile)
+ dl = src2lib.get(cxx,[])
+ if len(dl)==1:
+ l = dl[0]
+ elif len(dl)>1:
+ print "Got several libs(",len(dl),"for ",hfile,":"
+ print dl
+
+ if l =="external":
+ notassociatedfiles[hfile] = 1
+ else:
+ append(lib2lib[lib],l)
+
+ ###################### Debug parts...
+ if debug>=1:
for lib in libraries:
- print lib
- for f in allfiles[lib]:
- l = findLibrary(f,srcfiles)
- print " ",f,"(",l,")"
+ print libshorten(lib),"depends on"
+ for f in lib2lib[lib]:
+ print " ",libshorten(f)
+
+ if debug>=2:
+ print
+ print "From source files to include files : "
+ for cxxfile, hfile in src2inc.items():
+ print fileshorten(cxxfile,alice_root)
+ for h in hfile:
+ print " ",fileshorten(h,alice_root)
+
+ if debug>=3:
print
+ print "From include files to source files : "
+ for i,sources in inc2src.items():
+ print fileshorten(i,alice_root), len(sources)
+ for s in sources:
+ print " ",fileshorten(s,alice_root),"(",
+ for l in src2lib[s]:
+ print libshorten(l),
+ print ")"
+ ######################
+
+ if len(notassociatedfiles) > 0:
+ print "The following files could not be associated with any library..."
+ for f in notassociatedfiles.keys():
+ print f,
+ t=tryRecover(f,inc2src,src2lib,alice_root)
+ print t
- # deps is a dictionary
- # deps[libXXX.pkg] -> { list of libraries libXXX.pkg directly depends upon }
-
- deps = {}
-
- for lib,files in allfiles.items():
- d = []
- for f in files:
- l = findLibrary(f,srcfiles)
- if l != lib:
- d.append(l)
- deps[lib] = unique(d)
-
- if debug:
- for lib in deps:
- print lib, " depends on "
- for l in deps[lib]:
- print " ",l
- print
-
+ # output the dot file that will have to be processed by the dot program
+
ofile = "%s.dot" % os.path.splitext(os.path.basename(sys.argv[0]))[0]
f = open(ofile,"w")
- f.write("digraph G {")
- f.write("rankdir=BT;")
+ f.write("digraph G {\n")
+ f.write("rankdir=BT;\n")
- for l,d in deps.items():
+ defaultcolor = "lightblue"
+
+ colors = {}
+
+ colors["MUON"] = "lightyellow"
+ colors["STEER"] = "lightgray"
+
+ for l,d in lib2lib.items():
for dl in d:
- if re.search("MUON",dl):
- f.write("node [shape=box,style=filled,fillcolor=yellow];")
- else:
- f.write("node [shape=ellipse,style=filled,fillcolor=lightgray];")
- f.write("%s -> %s;\n" %(shorten(l),shorten(dl)))
+ f.write("%s->%s;\n" %(libshorten(l),libshorten(dl)))
- f.write("}")
+ for p in packages:
+ f.write("subgraph cluster_%s {\n" % p.lower())
+ color = colors.get(p,defaultcolor)
+ f.write("style=filled;\n")
+ f.write("color=%s;\n" % color)
+ f.write('label="%s";\n' % p)
+ for lib in packages[p]:
+ f.write("%s\n" % libshorten(lib))
+ f.write("}\n")
+
+ f.write("}\n")
print "You should now do :"
print "tred %s > %s.bis" % ( ofile, ofile )
- print "dot -Tpng %s.bis > %s.png" % (ofile,ofile)
+ print "dot -Tpng %s.bis -o %s.png" % (ofile,ofile)
if __name__ == "__main__":
main()