CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
TreeCrawler.py
Go to the documentation of this file.
1 # This CMS code is based on previous work done by Toby Dickenson, as indiciated below
2 #
3 # for questions: Benedikt.Hegner@cern.ch
4 
5 # Copyright 2004 Toby Dickenson
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject
13 # to the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26 import sys, os, inspect, copy, struct, dis, imp
27 import modulefinder
28 
30  return ".".join(name.replace("python/","").replace(".py","").split("/")[-3:])
31 
32 
33 class Color:
34  """ANSI escape display sequences"""
35  info = "\033[1;34m"
36  hilight = "\033[31m"
37  alternate = "\033[32m"
38  extra = "\033[33m"
39  backlight = "\033[43m"
40  underline = "\033[4m"
41  lessemphasis = "\033[30m"
42  deemphasis = "\033[1;30m"
43  none = "\033[0m"
44 
45 _stack = []
46 
47 class SearchHit:
48  pass
49 
50 class Package(object):
51  def __init__(self,name,top=False):
52  self.name = name
53  self.dependencies = []
54  self.searched = False
55  self.stack = []
56  if top:
57  self.module = None
58  else:
59  self.module = __import__(name,[],[],"*")
60  def dump(self,level):
61  indent = " " * level
62  print indent, "+", Color.info, self.name, Color.none
63  # sort dependencies alphabetically
64  self.dependencies.sort(key = lambda x: x.name)
65  for package in self.dependencies:
66  package.dump(level+1)
67  def search(self,pattern,result):
68  """ recursive search for pattern in source files"""
69  # first start searching in the package itself / do this only once
70  if self.module:
71  for number, line in enumerate(inspect.getsource(self.module).splitlines()):
72  if pattern in line:
73  filename = packageNameFromFilename(inspect.getsourcefile(self.module))
74  if not self.searched:
75  # save the hit, so we can add later stacks to it
76  self.hit = SearchHit()
77  self.hit.number = number
78  self.hit.filename = filename
79  self.hit.line = line
80  self.hit.stacks = list()
81  result.append(self.hit)
82  self.hit.stacks.append(copy.copy(_stack))
83  # then go on with dependencies
84  _stack.append(self.name)
85  for package in self.dependencies:
86  package.search(pattern,result)
87  _stack.pop()
88  self.searched = True
89 
90 
91 class mymf(modulefinder.ModuleFinder):
92  def __init__(self,*args,**kwargs):
93  self._depgraph = {}
94  self._types = {}
95  self._last_caller = None
96  #TODO - replace by environment variables CMSSW_BASE and CMSSW_RELEASE_BASE (*and* do it only if the global one is not empty like for IB areas)
97  self._localarea = os.path.expandvars('$CMSSW_BASE')
98  self._globalarea = os.path.expandvars('$CMSSW_RELEASE_BASE')
99  modulefinder.ModuleFinder.__init__(self,*args,**kwargs)
100  def import_hook(self, name, caller=None, fromlist=None, level=-1):
101  old_last_caller = self._last_caller
102  try:
103  self._last_caller = caller
104  return modulefinder.ModuleFinder.import_hook(self,name,caller,fromlist, level=level)
105  finally:
106  self._last_caller = old_last_caller
107 
108  def import_module(self,partnam,fqname,parent):
109 
110  if partnam in ("os","unittest"):
111  r = None
112  else:
113  r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
114  # since the modulefinder is not able to look into the global area when coming from the local area, we force a second try
115  if parent and not r and self._localarea != '' and self._globalarea != '':
116  parent.__file__ = parent.__file__.replace(self._localarea,self._globalarea)
117  parent.__path__[0] = parent.__path__[0].replace(self._localarea,self._globalarea)
118  r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
119 
120  if r is not None:
121  self._depgraph.setdefault(self._last_caller.__name__,{})[r.__name__] = 1
122  return r
123  def load_module(self, fqname, fp, pathname, aux_info):
124  (suffix, mode, type) = aux_info
125  r = modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type))
126  if r is not None:
127  self._types[r.__name__] = type
128  return r
129 
130  def scan_opcodes_25(self, co, unpack = struct.unpack):
131  """
132  This is basically just the default opcode scanner from ModuleFinder, but extended to also
133  look for "process.load(<module>)' commands. Since the Process object might not necassarily
134  be called "process", it scans for a call to a "load" method with a single parameter on
135  *any* object. If one is found it checks if the parameter is a string that refers to a valid
136  python module in the local or global area. If it does, the scanner assumes this was a call
137  to a Process object and yields the module name.
138  It's not possible to scan first for Process object declarations to get the name of the
139  objects since often (e.g. for customisation functions) the object is passed to a function
140  in a different file.
141 
142  The ModuleFinder.scan_opcodes_25 implementation this is based was taken from
143  https://hg.python.org/cpython/file/2.7/Lib/modulefinder.py#l364
144  """
145  # Scan the code, and yield 'interesting' opcode combinations
146  # Python 2.5 version (has absolute and relative imports)
147  code = co.co_code
148  names = co.co_names
149  consts = co.co_consts
150  LOAD_CONST = modulefinder.LOAD_CONST
151  IMPORT_NAME = modulefinder.IMPORT_NAME
152  STORE_OPS = modulefinder.STORE_OPS
153  HAVE_ARGUMENT = modulefinder.HAVE_ARGUMENT
154  LOAD_ATTR = chr(dis.opname.index('LOAD_ATTR'))
155  LOAD_NAME = chr(dis.opname.index('LOAD_NAME'))
156  CALL_FUNCTION = chr(dis.opname.index('CALL_FUNCTION'))
157  LOAD_LOAD_AND_IMPORT = LOAD_CONST + LOAD_CONST + IMPORT_NAME
158 
159  try :
160  indexOfLoadConst = names.index("load") # This might throw a ValueError
161  # These are the opcodes required to access the "load" attribute. This might
162  # not even be a function, but I check for that later.
163  loadMethodOpcodes = LOAD_ATTR+struct.pack('<H',indexOfLoadConst)
164  except ValueError :
165  # doesn't look like "load" is used anywhere in this file
166  loadMethodOpcodes=None
167 
168  while code:
169  c = code[0]
170 
171  # Check to see if this is a call to a "load" method
172  if loadMethodOpcodes!=None and len(code)>=9 : # Need at least 9 codes for the full call
173  if code[:3]==loadMethodOpcodes :
174  # The attribute "load" is being accessed, need to make sure this is a function call.
175  # I'll look ahead and see if the CALL_FUNCTION code is used - this could be in a different
176  # place depending on the number of arguments, but I'm only interested in methods with a
177  # single argument so I know exactly where CALL_FUNCTION should be.
178  if code[6]==CALL_FUNCTION :
179  # I know this is calling a method called "load" with one argument. I need
180  # to find out what the argument is. Note that I still don't know if this is
181  # on a cms.Process object.
182  indexInTable=unpack('<H',code[4:6])[0]
183  if code[3]==LOAD_CONST :
184  # The argument is a constant, so retrieve that from the table
185  loadMethodArgument=consts[indexInTable]
186  # I know a load method with one argument has been called on *something*, but I don't
187  # know if it was a cms.Process object. All I can do is check to see if the argument is
188  # a string, and if so if it refers to a python file in the user or global areas.
189  try :
190  loadMethodArgument = loadMethodArgument.replace("/",".")
191  # I can only use imp.find_module on submodules (i.e. each bit between a "."), so try
192  # that on each submodule in turn using the previously found filename. Note that I have
193  # to try this twice, because if the first pass traverses into a package in the local
194  # area but the subpackage has not been checked out it will report that the subpackage
195  # doesn't exist, even though it is available in the global area.
196  try :
197  parentFilename=[self._localarea+"/python"]
198  for subModule in loadMethodArgument.split(".") :
199  moduleInfo=imp.find_module( subModule, parentFilename )
200  parentFilename=[moduleInfo[1]]
201  # If control got this far without raising an exception, then it must be a valid python module
202  yield "import", (None, loadMethodArgument)
203  except ImportError :
204  # Didn't work in the local area, try in the global area.
205  parentFilename=[self._globalarea+"/python"]
206  for subModule in loadMethodArgument.split(".") :
207  moduleInfo=imp.find_module( subModule, parentFilename )
208  parentFilename=[moduleInfo[1]]
209  # If control got this far without raising an exception, then it must be a valid python module
210  yield "import", (None, loadMethodArgument)
211  except Exception as error:
212  # Either there was an import error (not a python module) or there was a string
213  # manipulaton error (argument not a string). Assume this wasn't a call on a
214  # cms.Process object and move on silently.
215  pass
216 
217  elif code[3]==LOAD_NAME :
218  # The argument is a variable. I can get the name of the variable quite easily but
219  # not the value, unless I execute all of the opcodes. Not sure what to do here,
220  # guess I'll just print a warning so that the user knows?
221  print "Unable to determine the value of variable '"+names[indexInTable]+"' to see if it is a proces.load(...) statement in file "+co.co_filename
222 
223  code=code[9:]
224  continue
225 
226  if c in STORE_OPS:
227  oparg, = unpack('<H', code[1:3])
228  yield "store", (names[oparg],)
229  code = code[3:]
230  continue
231  if code[:9:3] == LOAD_LOAD_AND_IMPORT:
232  oparg_1, oparg_2, oparg_3 = unpack('<xHxHxH', code[:9])
233  level = consts[oparg_1]
234  if level == -1: # normal import
235  yield "import", (consts[oparg_2], names[oparg_3])
236  elif level == 0: # absolute import
237  yield "absolute_import", (consts[oparg_2], names[oparg_3])
238  else: # relative import
239  yield "relative_import", (level, consts[oparg_2], names[oparg_3])
240  code = code[9:]
241  continue
242  if c >= HAVE_ARGUMENT:
243  code = code[3:]
244  else:
245  code = code[1:]
246 
247 def removeRecursiveLoops( node, verbose=False, currentStack=None ) :
248  if currentStack is None : currentStack=[]
249  try :
250  duplicateIndex=currentStack.index( node ) # If there isn't a recursive loop this will raise a ValueError
251  if verbose :
252  print "Removing recursive loop in:"
253  for index in xrange(duplicateIndex,len(currentStack)) :
254  print " ",currentStack[index].name,"-->"
255  print " ",node.name
256  currentStack[-1].dependencies.remove(node)
257  except ValueError:
258  # No recursive loop found, so continue traversing the tree
259  currentStack.append( node )
260  for subnode in node.dependencies :
261  removeRecursiveLoops( subnode, verbose, currentStack[:] )
262 
263 def transformIntoGraph(depgraph,toplevel):
264  packageDict = {}
265  # create the top level config
266  packageDict[toplevel] = Package(toplevel, top = True)
267 
268  # create package objects
269  for key, value in depgraph.iteritems():
270  if key.count(".") == 2 and key != toplevel:
271  packageDict[key] = Package(key)
272  for name in value.keys():
273  if name.count(".") == 2: packageDict[name] = Package(name)
274  # now create dependencies
275  for key, value in depgraph.iteritems():
276  if key.count(".") == 2 or key == toplevel:
277  package = packageDict[key]
278  package.dependencies = [packageDict[name] for name in value.keys() if name.count(".") == 2]
279 
280  removeRecursiveLoops( packageDict[toplevel] )
281  # find and return the top level config
282  return packageDict[toplevel]
283 
284 
285 def getDependenciesFromPythonFile(filename,toplevelname,path):
286  modulefinder = mymf(path)
287  modulefinder.run_script(filename)
288  globalDependencyDict = modulefinder._depgraph
289  globalDependencyDict[toplevelname] = globalDependencyDict["__main__"]
290  return globalDependencyDict
291 
292 
293 def getImportTree(filename,path):
294  toplevelname = packageNameFromFilename(filename)
295  # get dependencies from given file
296  globalDependencyDict = getDependenciesFromPythonFile(filename,toplevelname,path)
297 
298  # transform this flat structure in a dependency tree
299  dependencyGraph = transformIntoGraph(globalDependencyDict,toplevelname)
300  return dependencyGraph
def transformIntoGraph
Definition: TreeCrawler.py:263
def getDependenciesFromPythonFile
Definition: TreeCrawler.py:285
def removeRecursiveLoops
Definition: TreeCrawler.py:247
def packageNameFromFilename
Definition: TreeCrawler.py:29
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def getImportTree
Definition: TreeCrawler.py:293
double split
Definition: MVATrainer.cc:139
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run