CMS 3D CMS Logo

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 import six
29 
31  return ".".join(name.replace("python/","").replace(".py","").split("/")[-3:])
32 
33 
34 class Color:
35  """ANSI escape display sequences"""
36  info = "\033[1;34m"
37  hilight = "\033[31m"
38  alternate = "\033[32m"
39  extra = "\033[33m"
40  backlight = "\033[43m"
41  underline = "\033[4m"
42  lessemphasis = "\033[30m"
43  deemphasis = "\033[1;30m"
44  none = "\033[0m"
45 
46 _stack = []
47 
48 class SearchHit:
49  pass
50 
51 class Package(object):
52  def __init__(self,name,top=False):
53  self.name = name
54  self.dependencies = []
55  self.searched = False
56  self.stack = []
57  if top:
58  self.module = None
59  else:
60  self.module = __import__(name,[],[],"*")
61  def dump(self,level):
62  indent = " " * level
63  print indent, "+", Color.info, self.name, Color.none
64  # sort dependencies alphabetically
65  self.dependencies.sort(key = lambda x: x.name)
66  for package in self.dependencies:
67  package.dump(level+1)
68  def search(self,pattern,result):
69  """ recursive search for pattern in source files"""
70  # first start searching in the package itself / do this only once
71  if self.module:
72  for number, line in enumerate(inspect.getsource(self.module).splitlines()):
73  if pattern in line:
74  filename = packageNameFromFilename(inspect.getsourcefile(self.module))
75  if not self.searched:
76  # save the hit, so we can add later stacks to it
77  self.hit = SearchHit()
78  self.hit.number = number
79  self.hit.filename = filename
80  self.hit.line = line
81  self.hit.stacks = list()
82  result.append(self.hit)
83  self.hit.stacks.append(copy.copy(_stack))
84  # then go on with dependencies
85  _stack.append(self.name)
86  for package in self.dependencies:
87  package.search(pattern,result)
88  _stack.pop()
89  self.searched = True
90 
91 
92 class mymf(modulefinder.ModuleFinder):
93  def __init__(self,*args,**kwargs):
94  self._depgraph = {}
95  self._types = {}
96  self._last_caller = None
97  #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)
98  self._localarea = os.path.expandvars('$CMSSW_BASE')
99  self._globalarea = os.path.expandvars('$CMSSW_RELEASE_BASE')
100  modulefinder.ModuleFinder.__init__(self,*args,**kwargs)
101  def import_hook(self, name, caller=None, fromlist=None, level=-1):
102  old_last_caller = self._last_caller
103  try:
104  self._last_caller = caller
105  return modulefinder.ModuleFinder.import_hook(self,name,caller,fromlist, level=level)
106  finally:
107  self._last_caller = old_last_caller
108 
109  def import_module(self,partnam,fqname,parent):
110 
111  if partnam in ("os","unittest"):
112  r = None
113  else:
114  r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
115  # since the modulefinder is not able to look into the global area when coming from the local area, we force a second try
116  if parent and not r and self._localarea != '' and self._globalarea != '':
117  parent.__file__ = parent.__file__.replace(self._localarea,self._globalarea)
118  parent.__path__[0] = parent.__path__[0].replace(self._localarea,self._globalarea)
119  r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent)
120 
121  if r is not None:
122  self._depgraph.setdefault(self._last_caller.__name__,{})[r.__name__] = 1
123  return r
124  def load_module(self, fqname, fp, pathname, aux_info):
125  (suffix, mode, type) = aux_info
126  r = modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type))
127  if r is not None:
128  self._types[r.__name__] = type
129  return r
130 
131  def scan_opcodes_25(self, co, unpack = struct.unpack):
132  """
133  This is basically just the default opcode scanner from ModuleFinder, but extended to also
134  look for "process.load(<module>)' commands. Since the Process object might not necassarily
135  be called "process", it scans for a call to a "load" method with a single parameter on
136  *any* object. If one is found it checks if the parameter is a string that refers to a valid
137  python module in the local or global area. If it does, the scanner assumes this was a call
138  to a Process object and yields the module name.
139  It's not possible to scan first for Process object declarations to get the name of the
140  objects since often (e.g. for customisation functions) the object is passed to a function
141  in a different file.
142 
143  The ModuleFinder.scan_opcodes_25 implementation this is based was taken from
144  https://hg.python.org/cpython/file/2.7/Lib/modulefinder.py#l364
145  """
146  # Scan the code, and yield 'interesting' opcode combinations
147  # Python 2.5 version (has absolute and relative imports)
148  code = co.co_code
149  names = co.co_names
150  consts = co.co_consts
151  LOAD_CONST = modulefinder.LOAD_CONST
152  IMPORT_NAME = modulefinder.IMPORT_NAME
153  STORE_OPS = modulefinder.STORE_OPS
154  HAVE_ARGUMENT = modulefinder.HAVE_ARGUMENT
155  LOAD_ATTR = chr(dis.opname.index('LOAD_ATTR'))
156  LOAD_NAME = chr(dis.opname.index('LOAD_NAME'))
157  CALL_FUNCTION = chr(dis.opname.index('CALL_FUNCTION'))
158  LOAD_LOAD_AND_IMPORT = LOAD_CONST + LOAD_CONST + IMPORT_NAME
159 
160  try :
161  indexOfLoadConst = names.index("load") # This might throw a ValueError
162  # These are the opcodes required to access the "load" attribute. This might
163  # not even be a function, but I check for that later.
164  loadMethodOpcodes = LOAD_ATTR+struct.pack('<H',indexOfLoadConst)
165  except ValueError :
166  # doesn't look like "load" is used anywhere in this file
167  loadMethodOpcodes=None
168 
169  while code:
170  c = code[0]
171 
172  # Check to see if this is a call to a "load" method
173  if loadMethodOpcodes!=None and len(code)>=9 : # Need at least 9 codes for the full call
174  if code[:3]==loadMethodOpcodes :
175  # The attribute "load" is being accessed, need to make sure this is a function call.
176  # I'll look ahead and see if the CALL_FUNCTION code is used - this could be in a different
177  # place depending on the number of arguments, but I'm only interested in methods with a
178  # single argument so I know exactly where CALL_FUNCTION should be.
179  if code[6]==CALL_FUNCTION :
180  # I know this is calling a method called "load" with one argument. I need
181  # to find out what the argument is. Note that I still don't know if this is
182  # on a cms.Process object.
183  indexInTable=unpack('<H',code[4:6])[0]
184  if code[3]==LOAD_CONST :
185  # The argument is a constant, so retrieve that from the table
186  loadMethodArgument=consts[indexInTable]
187  # I know a load method with one argument has been called on *something*, but I don't
188  # know if it was a cms.Process object. All I can do is check to see if the argument is
189  # a string, and if so if it refers to a python file in the user or global areas.
190  try :
191  loadMethodArgument = loadMethodArgument.replace("/",".")
192  # I can only use imp.find_module on submodules (i.e. each bit between a "."), so try
193  # that on each submodule in turn using the previously found filename. Note that I have
194  # to try this twice, because if the first pass traverses into a package in the local
195  # area but the subpackage has not been checked out it will report that the subpackage
196  # doesn't exist, even though it is available in the global area.
197  try :
198  parentFilename=[self._localarea+"/python"]
199  for subModule in loadMethodArgument.split(".") :
200  moduleInfo=imp.find_module( subModule, parentFilename )
201  parentFilename=[moduleInfo[1]]
202  # If control got this far without raising an exception, then it must be a valid python module
203  yield "import", (None, loadMethodArgument)
204  except ImportError :
205  # Didn't work in the local area, try in the global area.
206  parentFilename=[self._globalarea+"/python"]
207  for subModule in loadMethodArgument.split(".") :
208  moduleInfo=imp.find_module( subModule, parentFilename )
209  parentFilename=[moduleInfo[1]]
210  # If control got this far without raising an exception, then it must be a valid python module
211  yield "import", (None, loadMethodArgument)
212  except Exception as error:
213  # Either there was an import error (not a python module) or there was a string
214  # manipulaton error (argument not a string). Assume this wasn't a call on a
215  # cms.Process object and move on silently.
216  pass
217 
218  elif code[3]==LOAD_NAME :
219  # The argument is a variable. I can get the name of the variable quite easily but
220  # not the value, unless I execute all of the opcodes. Not sure what to do here,
221  # guess I'll just print a warning so that the user knows?
222  print "Unable to determine the value of variable '"+names[indexInTable]+"' to see if it is a proces.load(...) statement in file "+co.co_filename
223 
224  code=code[9:]
225  continue
226 
227  if c in STORE_OPS:
228  oparg, = unpack('<H', code[1:3])
229  yield "store", (names[oparg],)
230  code = code[3:]
231  continue
232  if code[:9:3] == LOAD_LOAD_AND_IMPORT:
233  oparg_1, oparg_2, oparg_3 = unpack('<xHxHxH', code[:9])
234  level = consts[oparg_1]
235  if level == -1: # normal import
236  yield "import", (consts[oparg_2], names[oparg_3])
237  elif level == 0: # absolute import
238  yield "absolute_import", (consts[oparg_2], names[oparg_3])
239  else: # relative import
240  yield "relative_import", (level, consts[oparg_2], names[oparg_3])
241  code = code[9:]
242  continue
243  if c >= HAVE_ARGUMENT:
244  code = code[3:]
245  else:
246  code = code[1:]
247 
248 def removeRecursiveLoops( node, verbose=False, currentStack=None ) :
249  if currentStack is None : currentStack=[]
250  try :
251  duplicateIndex=currentStack.index( node ) # If there isn't a recursive loop this will raise a ValueError
252  if verbose :
253  print "Removing recursive loop in:"
254  for index in xrange(duplicateIndex,len(currentStack)) :
255  print " ",currentStack[index].name,"-->"
256  print " ",node.name
257  currentStack[-1].dependencies.remove(node)
258  except ValueError:
259  # No recursive loop found, so continue traversing the tree
260  currentStack.append( node )
261  for subnode in node.dependencies :
262  removeRecursiveLoops( subnode, verbose, currentStack[:] )
263 
264 def transformIntoGraph(depgraph,toplevel):
265  packageDict = {}
266  # create the top level config
267  packageDict[toplevel] = Package(toplevel, top = True)
268 
269  # create package objects
270  for key, value in six.iteritems(depgraph):
271  if key.count(".") == 2 and key != toplevel:
272  packageDict[key] = Package(key)
273  for name in value.keys():
274  if name.count(".") == 2: packageDict[name] = Package(name)
275  # now create dependencies
276  for key, value in six.iteritems(depgraph):
277  if key.count(".") == 2 or key == toplevel:
278  package = packageDict[key]
279  package.dependencies = [packageDict[name] for name in value.keys() if name.count(".") == 2]
280 
281  removeRecursiveLoops( packageDict[toplevel] )
282  # find and return the top level config
283  return packageDict[toplevel]
284 
285 
286 def getDependenciesFromPythonFile(filename,toplevelname,path):
287  modulefinder = mymf(path)
288  modulefinder.run_script(filename)
289  globalDependencyDict = modulefinder._depgraph
290  globalDependencyDict[toplevelname] = globalDependencyDict["__main__"]
291  return globalDependencyDict
292 
293 
294 def getImportTree(filename,path):
295  toplevelname = packageNameFromFilename(filename)
296  # get dependencies from given file
297  globalDependencyDict = getDependenciesFromPythonFile(filename,toplevelname,path)
298 
299  # transform this flat structure in a dependency tree
300  dependencyGraph = transformIntoGraph(globalDependencyDict,toplevelname)
301  return dependencyGraph
def import_hook(self, name, caller=None, fromlist=None, level=-1)
Definition: TreeCrawler.py:101
def replace(string, replacements)
def __init__(self, name, top=False)
Definition: TreeCrawler.py:52
def getDependenciesFromPythonFile(filename, toplevelname, path)
Definition: TreeCrawler.py:286
def __init__(self, args, kwargs)
Definition: TreeCrawler.py:93
def import_module(self, partnam, fqname, parent)
Definition: TreeCrawler.py:109
def getImportTree(filename, path)
Definition: TreeCrawler.py:294
def packageNameFromFilename(name)
Definition: TreeCrawler.py:30
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def removeRecursiveLoops(node, verbose=False, currentStack=None)
Definition: TreeCrawler.py:248
def search(self, pattern, result)
Definition: TreeCrawler.py:68
def transformIntoGraph(depgraph, toplevel)
Definition: TreeCrawler.py:264
def dump(self, level)
Definition: TreeCrawler.py:61
def scan_opcodes_25(self, co, unpack=struct.unpack)
Definition: TreeCrawler.py:131
def load_module(self, fqname, fp, pathname, aux_info)
Definition: TreeCrawler.py:124
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