File indexing completed on 2024-04-06 12:12:54
0001 from __future__ import print_function
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027 from builtins import range
0028 import sys, os, inspect, copy, struct, dis, imp
0029 import modulefinder
0030
0031 def packageNameFromFilename(name:str) -> str:
0032 return ".".join(name.replace("python/","").replace(".py","").split("/")[-3:])
0033
0034
0035 class Color:
0036 """ANSI escape display sequences"""
0037 info = "\033[1;34m"
0038 hilight = "\033[31m"
0039 alternate = "\033[32m"
0040 extra = "\033[33m"
0041 backlight = "\033[43m"
0042 underline = "\033[4m"
0043 lessemphasis = "\033[30m"
0044 deemphasis = "\033[1;30m"
0045 none = "\033[0m"
0046
0047 _stack = []
0048
0049 class SearchHit:
0050 pass
0051
0052 class Package(object):
0053 def __init__(self,name,top=False):
0054 self.name = name
0055 self.dependencies = []
0056 self.searched = False
0057 self.stack = []
0058 if top:
0059 self.module = None
0060 else:
0061 self.module = __import__(name,[],[],"*")
0062 def dump(self,level:int):
0063 indent = " " * level
0064 print(indent, "+", Color.info, self.name, Color.none)
0065
0066 self.dependencies.sort(key = lambda x: x.name)
0067 for package in self.dependencies:
0068 package.dump(level+1)
0069 def search(self,pattern,result):
0070 """ recursive search for pattern in source files"""
0071
0072 if self.module:
0073 for number, line in enumerate(inspect.getsource(self.module).splitlines()):
0074 if pattern in line:
0075 filename = packageNameFromFilename(inspect.getsourcefile(self.module))
0076 if not self.searched:
0077
0078 self.hit = SearchHit()
0079 self.hit.number = number
0080 self.hit.filename = filename
0081 self.hit.line = line
0082 self.hit.stacks = list()
0083 result.append(self.hit)
0084 self.hit.stacks.append(copy.copy(_stack))
0085
0086 _stack.append(self.name)
0087 for package in self.dependencies:
0088 package.search(pattern,result)
0089 _stack.pop()
0090 self.searched = True
0091
0092
0093 class mymf(modulefinder.ModuleFinder):
0094 def __init__(self,*args,**kwargs):
0095 self._depgraph = {}
0096 self._types = {}
0097 self._last_caller = None
0098
0099 self._localarea = os.path.expandvars('$CMSSW_BASE')
0100 self._globalarea = os.path.expandvars('$CMSSW_RELEASE_BASE')
0101 modulefinder.ModuleFinder.__init__(self,*args,**kwargs)
0102 def import_hook(self, name, caller=None, fromlist=None, level=-1):
0103 old_last_caller = self._last_caller
0104 try:
0105 self._last_caller = caller
0106 return modulefinder.ModuleFinder.import_hook(self,name,caller,fromlist, level=level)
0107 finally:
0108 self._last_caller = old_last_caller
0109
0110 def import_module(self,partnam,fqname,parent):
0111
0112 if partnam in ("os","unittest"):
0113 r = None
0114 else:
0115 r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
0116
0117 if parent and not r and self._localarea != '' and self._globalarea != '':
0118 parent.__file__ = parent.__file__.replace(self._localarea,self._globalarea)
0119 parent.__path__[0] = parent.__path__[0].replace(self._localarea,self._globalarea)
0120 r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
0121
0122 if r is not None:
0123 self._depgraph.setdefault(self._last_caller.__name__,{})[r.__name__] = 1
0124 return r
0125 def load_module(self, fqname, fp, pathname, aux_info):
0126 (suffix, mode, type) = aux_info
0127 r = modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type))
0128 if r is not None:
0129 self._types[r.__name__] = type
0130 return r
0131
0132 def scan_opcodes_25(self, co, unpack = struct.unpack):
0133 """
0134 This is basically just the default opcode scanner from ModuleFinder, but extended to also
0135 look for "process.load(<module>)' commands. Since the Process object might not necassarily
0136 be called "process", it scans for a call to a "load" method with a single parameter on
0137 *any* object. If one is found it checks if the parameter is a string that refers to a valid
0138 python module in the local or global area. If it does, the scanner assumes this was a call
0139 to a Process object and yields the module name.
0140 It's not possible to scan first for Process object declarations to get the name of the
0141 objects since often (e.g. for customisation functions) the object is passed to a function
0142 in a different file.
0143
0144 The ModuleFinder.scan_opcodes_25 implementation this is based was taken from
0145 https://hg.python.org/cpython/file/2.7/Lib/modulefinder.py#l364
0146 """
0147
0148
0149 code = co.co_code
0150 names = co.co_names
0151 consts = co.co_consts
0152 LOAD_CONST = modulefinder.LOAD_CONST
0153 IMPORT_NAME = modulefinder.IMPORT_NAME
0154 STORE_OPS = modulefinder.STORE_OPS
0155 HAVE_ARGUMENT = modulefinder.HAVE_ARGUMENT
0156 LOAD_ATTR = chr(dis.opname.index('LOAD_ATTR'))
0157 LOAD_NAME = chr(dis.opname.index('LOAD_NAME'))
0158 CALL_FUNCTION = chr(dis.opname.index('CALL_FUNCTION'))
0159 LOAD_LOAD_AND_IMPORT = LOAD_CONST + LOAD_CONST + IMPORT_NAME
0160
0161 try :
0162 indexOfLoadConst = names.index("load")
0163
0164
0165 loadMethodOpcodes = LOAD_ATTR+struct.pack('<H',indexOfLoadConst)
0166 except ValueError :
0167
0168 loadMethodOpcodes=None
0169
0170 while code:
0171 c = code[0]
0172
0173
0174 if loadMethodOpcodes!=None and len(code)>=9 :
0175 if code[:3]==loadMethodOpcodes :
0176
0177
0178
0179
0180 if code[6]==CALL_FUNCTION :
0181
0182
0183
0184 indexInTable=unpack('<H',code[4:6])[0]
0185 if code[3]==LOAD_CONST :
0186
0187 loadMethodArgument=consts[indexInTable]
0188
0189
0190
0191 try :
0192 loadMethodArgument = loadMethodArgument.replace("/",".")
0193
0194
0195
0196
0197
0198 try :
0199 parentFilename=[self._localarea+"/python"]
0200 for subModule in loadMethodArgument.split(".") :
0201 moduleInfo=imp.find_module( subModule, parentFilename )
0202 parentFilename=[moduleInfo[1]]
0203
0204 yield "import", (None, loadMethodArgument)
0205 except ImportError :
0206
0207 parentFilename=[self._globalarea+"/python"]
0208 for subModule in loadMethodArgument.split(".") :
0209 moduleInfo=imp.find_module( subModule, parentFilename )
0210 parentFilename=[moduleInfo[1]]
0211
0212 yield "import", (None, loadMethodArgument)
0213 except Exception as error:
0214
0215
0216
0217 pass
0218
0219 elif code[3]==LOAD_NAME :
0220
0221
0222
0223 print("Unable to determine the value of variable '"+names[indexInTable]+"' to see if it is a proces.load(...) statement in file "+co.co_filename)
0224
0225 code=code[9:]
0226 continue
0227
0228 if c in STORE_OPS:
0229 oparg, = unpack('<H', code[1:3])
0230 yield "store", (names[oparg],)
0231 code = code[3:]
0232 continue
0233 if code[:9:3] == LOAD_LOAD_AND_IMPORT:
0234 oparg_1, oparg_2, oparg_3 = unpack('<xHxHxH', code[:9])
0235 level = consts[oparg_1]
0236 if level == -1:
0237 yield "import", (consts[oparg_2], names[oparg_3])
0238 elif level == 0:
0239 yield "absolute_import", (consts[oparg_2], names[oparg_3])
0240 else:
0241 yield "relative_import", (level, consts[oparg_2], names[oparg_3])
0242 code = code[9:]
0243 continue
0244 if c >= HAVE_ARGUMENT:
0245 code = code[3:]
0246 else:
0247 code = code[1:]
0248
0249 def removeRecursiveLoops( node, verbose=False, currentStack=None ) :
0250 if currentStack is None : currentStack=[]
0251 try :
0252 duplicateIndex=currentStack.index( node )
0253 if verbose :
0254 print("Removing recursive loop in:")
0255 for index in range(duplicateIndex,len(currentStack)) :
0256 print(" ",currentStack[index].name,"-->")
0257 print(" ",node.name)
0258 currentStack[-1].dependencies.remove(node)
0259 except ValueError:
0260
0261 currentStack.append( node )
0262 for subnode in node.dependencies :
0263 removeRecursiveLoops( subnode, verbose, currentStack[:] )
0264
0265 def transformIntoGraph(depgraph,toplevel):
0266 packageDict = {}
0267
0268 packageDict[toplevel] = Package(toplevel, top = True)
0269
0270
0271 for key, value in depgraph.items():
0272 if key.count(".") == 2 and key != toplevel:
0273 packageDict[key] = Package(key)
0274 for name in value.keys():
0275 if name.count(".") == 2: packageDict[name] = Package(name)
0276
0277 for key, value in depgraph.items():
0278 if key.count(".") == 2 or key == toplevel:
0279 package = packageDict[key]
0280 package.dependencies = [packageDict[name] for name in value.keys() if name.count(".") == 2]
0281
0282 removeRecursiveLoops( packageDict[toplevel] )
0283
0284 return packageDict[toplevel]
0285
0286
0287 def getDependenciesFromPythonFile(filename:str,toplevelname,path):
0288 modulefinder = mymf(path)
0289 modulefinder.run_script(filename)
0290 globalDependencyDict = modulefinder._depgraph
0291 globalDependencyDict[toplevelname] = globalDependencyDict["__main__"]
0292 return globalDependencyDict
0293
0294
0295 def getImportTree(filename:str,path):
0296 toplevelname = packageNameFromFilename(filename)
0297
0298 globalDependencyDict = getDependenciesFromPythonFile(filename,toplevelname,path)
0299
0300
0301 dependencyGraph = transformIntoGraph(globalDependencyDict,toplevelname)
0302 return dependencyGraph