All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
genconfuser.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Generate _confDb.py files for ConfigurableUser classes.
5 """
6 
7 import os
8 import sys
9 import time
10 import logging
12 
13 from pprint import pformat
14 from glob import glob
15 from GaudiKernel.ConfigurableDb import cfgDb
16 
17 logging.VERBOSE = (logging.INFO + logging.DEBUG) / 2
18 logging.addLevelName(logging.VERBOSE, "VERBOSE")
19 logging.verbose = lambda msg, *args, **kwargs: \
20  apply(logging.log, (logging.VERBOSE, msg) + args, kwargs)
21 
22 def _inheritsfrom(derived, basename):
23  """
24  Check if the class name 'basename' is anywhere in the base classes of the
25  class 'derived'.
26  If 'derived' _is_ 'basename', returns False.
27  """
28  for b in derived.__bases__:
29  if b.__name__ == basename:
30  return True
31  else:
32  if _inheritsfrom(b, basename):
33  return True
34  return False
35 
37  '''
38  Equivalent to GaudiKernel.ConfigurableDb.loadConfigurableDb(), but does a
39  deep search and executes the '*.confdb' files instead of importing them.
40  '''
41  log = GaudiKernel.ConfigurableDb.log
42  from os.path import join as path_join
43  # look for the confdb files in all the reasonable places
44  # - CMake builds
45  confDbFiles = []
46  for path in sys.path:
47  confDbFiles += [f for f in glob(path_join(path, '*', '*.confdb'))
48  if os.path.isfile(f)]
49  # - used projects and local merged file
50  pathlist = os.getenv("LD_LIBRARY_PATH", "").split(os.pathsep)
51  for path in filter(os.path.isdir, pathlist):
52  confDbFiles += [f for f in [path_join(path, f) for f in os.listdir(path)
53  if f.endswith('.confdb')]]
54  # - load the confdb files
55  for confDb in confDbFiles:
56  log.debug( "\t-loading [%s]..." % confDb )
57  try:
58  cfgDb._loadModule( confDb )
59  except Exception, err:
60  # It may happen that the file is found but not completely
61  # written, usually during parallel builds, but we do not care.
62  log.warning( "Could not load file [%s] !", confDb )
63  log.warning( "Reason: %s", err )
64  # top up with the regular merged confDb (for the used projects)
66 
67 def getConfigurableUsers(modulename, root, mayNotExist = False):
68  """
69  Find in the module 'modulename' all the classes that derive from ConfigurableUser.
70  Return the list of the names.
71  The flag mayNotExist is used to choose the level of the logging message in case
72  the requested module does not exist.
73  """
74  # remember the old system path
75  oldpath = list(sys.path)
76  # we need to hack the sys.path to add the first part of the module name after root
77  moduleelements = modulename.split('.')
78  if len(moduleelements) > 1:
79  moddir = os.sep.join([root] + moduleelements[:-1])
80  else:
81  moddir = root
82  # this is the name of the submodule to import
83  shortmodname = moduleelements[-1]
84  # check if the module file actually exists
85  if not os.path.exists(os.path.join(moddir, shortmodname) + ".py"):
86  msg = "Module %s does not exist" % modulename
87  if mayNotExist:
88  logging.verbose(msg)
89  else:
90  logging.error(msg)
91  # no file -> do not try to import
92  return []
93  # prepend moddir to the path
94  sys.path.insert(0, moddir)
95  logging.verbose("sys.path prepended with %r", sys.path[0])
96 
97  logging.info("Looking for ConfigurableUser in %r", modulename)
98  g, l = {}, {}
99  try:
100  logging.verbose("importing %s", shortmodname)
101  exec "import %s as mod" % shortmodname in g, l
102  finally:
103  # restore old sys.path
104  logging.verbose("restoring old sys.path")
105  sys.path = oldpath
106  mod = l["mod"]
107  if "__all__" in dir(mod) and mod.__all__:
108  all = mod.__all__
109  else:
110  all = [ n for n in dir(mod) if not n.startswith("_")]
111  result = []
112  for name in all:
113  cfg = cfgDb.get(name)
114  if cfg and cfg["module"] != modulename:
115  # This name comes from another module
116  logging.verbose("Object %r already found in module %r", name, cfg["module"])
117  continue
118  t = getattr(mod, name)
119  if isinstance(t, type) and _inheritsfrom(t, "ConfigurableUser"):
120  result.append(name)
121  logging.verbose("Found %r", result)
122  return result
123 
124 def main():
125  from optparse import OptionParser
126  parser = OptionParser(prog = os.path.basename(sys.argv[0]),
127  usage = "%prog [options] <PackageName> [<Module1> ...]")
128  parser.add_option("-o", "--output", action="store", type="string",
129  help="output file for confDb data [default = '../genConf/<PackageName>_user_confDb.py'].")
130  parser.add_option("-r", "--root", action="store", type="string",
131  help="root directory of the python modules [default = '../python'].")
132  parser.add_option("-v", "--verbose", action="store_true",
133  help="print some debugging information")
134  parser.add_option("--debug", action="store_true",
135  help="print more debugging information")
136  parser.add_option("--lockerpath", action="store",
137  metavar = "DIRNAME",
138  help="directory where to find the module 'locker'")
139  parser.set_defaults(root = os.path.join("..","python"))
140 
141  opts, args = parser.parse_args()
142 
143  if opts.debug:
144  log_level = logging.DEBUG
145  elif opts.verbose:
146  log_level = logging.VERBOSE
147  else:
148  log_level = logging.INFO
149  logging.basicConfig(format = "%(levelname)s: %(message)s",
150  stream = sys.stdout,
151  level = log_level)
152 
153  if len(args) < 1:
154  parser.error("PackageName is required")
155 
156  package_name = args.pop(0)
157 
158  usingConvention = False
159  if not args:
160  # use the conventional module name <package>.Configuration
161  args = [package_name + ".Configuration"]
162  usingConvention = True
163 
164  genConfDir = os.path.join("..", os.environ.get("CMTCONFIG", ""), "genConf")
165  if not os.path.exists(genConfDir):
166  genConfDir = os.path.join("..", "genConf")
167 
168  if not opts.output:
169  outputfile = os.path.join(genConfDir, package_name + '_user.confdb')
170  else:
171  outputfile = opts.output
172 
173 
174  # The locking ensures that nobody tries to modify the python.zip file while
175  # we read it.
176  dbLock = None
177  if "GAUDI_BUILD_LOCK" in os.environ:
178  if opts.lockerpath:
179  sys.path.append(opts.lockerpath)
180  # Get the LockFile class from the locker module in GaudiPolicy or use a fake
181  # factory.
182  try:
183  from locker import LockFile
184  except ImportError:
185  def LockFile(*args, **kwargs):
186  return None
187  # obtain the lock
188  dbLock = LockFile(os.environ["GAUDI_BUILD_LOCK"], temporary = True)
189 
190  # We can disable the error on missing configurables only if we can import Gaudi.Configurables
191  # It must be done at this point because it may conflict with logging.basicConfig
192  try:
193  import Gaudi.Configurables
194  Gaudi.Configurables.ignoreMissingConfigurables = True
195  except:
196  pass
197  # load configurables database to avoid fake duplicates
199  # ensure that local configurables are in the database
200  try:
201  # Add the local python directories to the python path to be able to import the local
202  # configurables
203  sys.path.insert(0, genConfDir)
204  sys.path.insert(0, os.path.join("..", "python"))
205  localConfDb = os.path.join(genConfDir, package_name, package_name + '.confdb')
206  if os.path.exists(localConfDb):
207  cfgDb._loadModule(localConfDb)
208  # Extend the search path of the package module to find the configurables
209  package_module = __import__(package_name)
210  package_module.__path__.insert(0, os.path.join(genConfDir, package_name))
211  except:
212  pass # ignore failures (not important)
213  del dbLock # Now we can let the others operate on the install area python directory
214 
215  # Collecting ConfigurableUser specializations
216  cus = {}
217  for mod in args:
218  lst = None
219  try:
220  lst = getConfigurableUsers(mod, root = opts.root, mayNotExist = usingConvention)
221  except ImportError:
222  import traceback
223  logging.error("Cannot import module %r:\n%s", mod,
224  traceback.format_exc().rstrip()) # I remove the trailing '\n'
225  return 2
226  if lst:
227  cus[mod] = lst
228  # Add the configurables to the database as fake entries to avoid duplicates
229  for m in lst:
230  cfgDb.add(configurable = m,
231  package = 'None',
232  module = 'None',
233  lib = 'None')
234  elif not usingConvention:
235  logging.warning("Specified module %r does not contain ConfigurableUser specializations", mod)
236 
237  if cus:
238  logging.info("ConfigurableUser found:\n%s", pformat(cus))
239  # header
240  output = """## -*- ascii -*-
241 # db file automatically generated by %s on: %s
242 """ % (parser.prog, time.asctime())
243 
244  for mod in cus:
245  for cu in cus[mod]:
246  output += "%s %s %s\n" % (mod, 'None', cu)
247 
248  # trailer
249  output += "## %s\n" % package_name
250  elif usingConvention:
251  logging.info("No ConfigurableUser found")
252  output = ("# db file automatically generated by %s on: %s\n"
253  "# No ConfigurableUser specialization in %s\n") % (parser.prog, time.asctime(), package_name)
254  else:
255  logging.error("No ConfigurableUser specialization found")
256  return 1
257 
258  # create the destination directory if not there
259  output_dir = os.path.dirname(outputfile)
260  try:
261  logging.info("Creating directory %r", output_dir)
262  os.makedirs(output_dir, 0755)
263  except OSError, err:
264  import errno
265  if err.errno == errno.EEXIST:
266  # somebody already - perhaps concurrently - created that dir.
267  pass
268  else:
269  raise
270 
271  # write output to file
272  logging.verbose("Writing confDb data to %r", outputfile)
273  open(outputfile, "w").write(output)
274  return 0
275 
276 if __name__ == '__main__':
277  retcode = main()
278  sys.exit(retcode)
def getConfigurableUsers
Definition: genconfuser.py:67
def _inheritsfrom
Definition: genconfuser.py:22
def loadConfigurableDb
Definition: genconfuser.py:36
def loadConfigurableDb
Helper function to load all ConfigurableDb files holding informations.