The Gaudi Framework  v36r16 (ea80daf8)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
genconfuser.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
12 """
13 Generate _confDb.py files for ConfigurableUser classes.
14 """
15 from __future__ import division
16 
17 import logging
18 import os
19 import sys
20 import time
21 from glob import glob
22 from pprint import pformat
23 
25 from GaudiKernel.ConfigurableDb import cfgDb
26 
27 logging.VERBOSE = (logging.INFO + logging.DEBUG) // 2
28 logging.addLevelName(logging.VERBOSE, "VERBOSE")
29 logging.verbose = lambda msg, *args, **kwargs: logging.log(
30  logging.VERBOSE, msg, *args, **kwargs
31 )
32 
33 
34 def _inheritsfrom(derived, basenames):
35  """
36  Check if any of the class names in 'basenames' is anywhere in the base
37  classes of the class 'derived'.
38  If 'derived' _is_ one in 'basenames', returns False.
39 
40  'basenames' can be a string or an iterable (of strings).
41  """
42  if isinstance(basenames, str):
43  basenames = (basenames,)
44  for b in derived.__bases__:
45  if b.__name__ in basenames:
46  return True
47  else:
48  if _inheritsfrom(b, basenames):
49  return True
50  return False
51 
52 
53 def loadConfigurableDb(build_dir=None, project_name=None):
54  """
55  Equivalent to GaudiKernel.ConfigurableDb.loadConfigurableDb(), but does a
56  deep search and executes the '*.confdb' files instead of importing them.
57  """
58  log = GaudiKernel.ConfigurableDb.log
59  from os.path import join as path_join
60 
61  # look for the confdb files in all the reasonable places
62  # - CMake builds
63  confDbFiles = []
64  for path in sys.path:
65  confDbFiles += [
66  f for f in glob(path_join(path, "*", "*.confdb")) if os.path.isfile(f)
67  ]
68  # - new-style CMake builds (look for all .confdb in the build tree)
69  if build_dir:
70  for root, _, files in os.walk(build_dir):
71  confDbFiles += [
72  os.path.join(root, f) for f in files if f.endswith(".confdb")
73  ]
74  # - used projects and local merged file
75  pathlist = os.getenv("LD_LIBRARY_PATH", "").split(os.pathsep)
76  for path in filter(os.path.isdir, pathlist):
77  confDbFiles += [
78  f
79  for f in [
80  path_join(path, f) for f in os.listdir(path) if f.endswith(".confdb")
81  ]
82  ]
83  # - get the list of ignored files
84  ignored_files = set(os.environ.get("CONFIGURABLE_DB_IGNORE", "").split(","))
85  # - load the confdb files
86  for confDb in set(confDbFiles):
87  if confDb in ignored_files or (
88  project_name
89  and os.path.basename(confDb) == ("{}.confdb".format(project_name))
90  ):
91  # skip ignored files and the project's own confdb
92  log.debug("\t-ignoring [%s]", confDb)
93  # make sure we count also <project_name>.confdb for GaudiKernel
94  ignored_files.add(confDb)
95  continue
96  log.debug("\t-loading [%s]...", confDb)
97  try:
98  cfgDb._loadModule(confDb)
99  except Exception as err:
100  # It may happen that the file is found but not completely
101  # written, usually during parallel builds, but we do not care.
102  log.warning("Could not load file [%s] !", confDb)
103  log.warning("Reason: %s", err)
104  # top up with the regular merged confDb (for the used projects)
105  # (make sure GaudiKernel gets the right exclusions)
106  os.environ["CONFIGURABLE_DB_IGNORE"] = ",".join(ignored_files)
108 
109 
110 def getConfigurableUsers(modulename, root, mayNotExist=False):
111  """
112  Find in the module 'modulename' all the classes that derive from ConfigurableUser.
113  Return the list of the names.
114  The flag mayNotExist is used to choose the level of the logging message in case
115  the requested module does not exist.
116  """
117  # remember the old system path
118  oldpath = list(sys.path)
119  # we need to hack the sys.path to add the first part of the module name after root
120  moduleelements = modulename.split(".")
121  if len(moduleelements) > 1:
122  moddir = os.sep.join([root] + moduleelements[:-1])
123  else:
124  moddir = root
125  # this is the name of the submodule to import
126  shortmodname = moduleelements[-1]
127  # check if the module file actually exists
128  if not os.path.exists(os.path.join(moddir, shortmodname) + ".py"):
129  msg = "Module %s does not exist" % modulename
130  if mayNotExist:
131  logging.verbose(msg)
132  else:
133  logging.error(msg)
134  # no file -> do not try to import
135  return []
136  # prepend moddir to the path
137  sys.path.insert(0, moddir)
138  logging.verbose("sys.path prepended with %r", sys.path[0])
139 
140  logging.info("Looking for ConfigurableUser in %r", modulename)
141  g, l = {}, {}
142  try:
143  logging.verbose("importing %s", shortmodname)
144  exec("import %s as mod" % shortmodname, g, l)
145  finally:
146  # restore old sys.path
147  logging.verbose("restoring old sys.path")
148  sys.path = oldpath
149  mod = l["mod"]
150  if "__all__" in dir(mod) and mod.__all__:
151  all = mod.__all__
152  else:
153  all = [n for n in dir(mod) if not n.startswith("_")]
154  result = []
155  for name in all:
156  cfg = cfgDb.get(name)
157  if cfg and cfg["module"] != modulename:
158  # This name comes from another module
159  logging.verbose("Object %r already found in module %r", name, cfg["module"])
160  continue
161  t = getattr(mod, name)
162  if isinstance(t, type) and _inheritsfrom(
163  t, ("ConfigurableUser", "SuperAlgorithm")
164  ):
165  result.append(name)
166  logging.verbose("Found %r", result)
167  return result
168 
169 
170 def main():
171  from optparse import OptionParser
172 
173  parser = OptionParser(
174  prog=os.path.basename(sys.argv[0]),
175  usage="%prog [options] <PackageName> [<Module1> ...]",
176  )
177  parser.add_option(
178  "-o",
179  "--output",
180  action="store",
181  type="string",
182  help="output file for confDb data [default = '../genConfDir/<PackageName>_user_confDb.py'].",
183  )
184  parser.add_option(
185  "-r",
186  "--root",
187  action="store",
188  type="string",
189  help="root directory of the python modules [default = '../python'].",
190  )
191  parser.add_option(
192  "-v", "--verbose", action="store_true", help="print some debugging information"
193  )
194  parser.add_option(
195  "--debug", action="store_true", help="print more debugging information"
196  )
197  parser.add_option(
198  "--build-dir",
199  action="store",
200  help="build directory where to look for .confdb files (search all subdirectories)",
201  )
202  parser.add_option(
203  "--project-name",
204  action="store",
205  help="name of the current project (used to exclude spurious versions of the .confdb file for the current project)",
206  )
207  parser.set_defaults(root=os.path.join("..", "python"))
208 
209  opts, args = parser.parse_args()
210 
211  if opts.debug:
212  log_level = logging.DEBUG
213  elif opts.verbose:
214  log_level = logging.VERBOSE
215  else:
216  log_level = logging.INFO if os.environ.get("VERBOSE") else logging.WARNING
217  logging.basicConfig(
218  format="%(levelname)s: %(message)s", stream=sys.stdout, level=log_level
219  )
220 
221  if len(args) < 1:
222  parser.error("PackageName is required")
223 
224  package_name = args.pop(0)
225 
226  usingConvention = False
227  if not args:
228  # use the conventional module name <package>.Configuration
229  args = [package_name + ".Configuration"]
230  usingConvention = True
231 
232  genConfDir = os.path.join("..", os.environ.get("CMTCONFIG", ""), "genConfDir")
233  if not os.path.exists(genConfDir):
234  genConfDir = os.path.join("..", "genConfDir")
235 
236  if not opts.output:
237  outputfile = os.path.join(genConfDir, package_name + "_user.confdb")
238  else:
239  outputfile = opts.output
240 
241  # We can disable the error on missing configurables only if we can import Gaudi.Configurables
242  # It must be done at this point because it may conflict with logging.basicConfig
243  try:
244  import Gaudi.Configurables
245 
246  Gaudi.Configurables.ignoreMissingConfigurables = True
247  except ImportError:
248  pass
249  # load configurables database to avoid fake duplicates
250  loadConfigurableDb(opts.build_dir, opts.project_name)
251  # ensure that local configurables are in the database
252  try:
253  # Add the local python directories to the python path to be able to import the local
254  # configurables
255  sys.path.insert(0, genConfDir)
256  sys.path.insert(0, os.path.join("..", "python"))
257  localConfDb = os.path.join(genConfDir, package_name, package_name + ".confdb")
258  if os.path.exists(localConfDb):
259  cfgDb._loadModule(localConfDb)
260  # Extend the search path of the package module to find the configurables
261  package_module = __import__(package_name)
262  package_module.__path__.insert(0, os.path.join(genConfDir, package_name))
263  except Exception:
264  pass # ignore failures (not important)
265 
266  # Collecting ConfigurableUser specializations
267  cus = {}
268  for mod in args:
269  lst = None
270  try:
271  lst = getConfigurableUsers(mod, root=opts.root, mayNotExist=usingConvention)
272  except ImportError:
273  import traceback
274 
275  logging.error(
276  "Cannot import module %r:\n%s", mod, traceback.format_exc().rstrip()
277  ) # I remove the trailing '\n'
278  return 2
279  if lst:
280  cus[mod] = lst
281  # Add the configurables to the database as fake entries to avoid duplicates
282  for m in lst:
283  cfgDb.add(configurable=m, package="None", module="None", lib="None")
284  elif not usingConvention:
285  logging.warning(
286  "Specified module %r does not contain ConfigurableUser specializations",
287  mod,
288  )
289 
290  if cus:
291  logging.info("ConfigurableUser found:\n%s", pformat(cus))
292  # header
293  output = """## -*- ascii -*-
294 # db file automatically generated by %s on: %s
295 """ % (
296  parser.prog,
297  time.asctime(),
298  )
299 
300  for mod in cus:
301  for cu in cus[mod]:
302  output += "%s %s %s\n" % (mod, "None", cu)
303 
304  # trailer
305  output += "## %s\n" % package_name
306  elif usingConvention:
307  logging.info("No ConfigurableUser found")
308  output = (
309  "# db file automatically generated by %s on: %s\n"
310  "# No ConfigurableUser specialization in %s\n"
311  ) % (parser.prog, time.asctime(), package_name)
312  else:
313  logging.error("No ConfigurableUser specialization found")
314  return 1
315 
316  # create the destination directory if not there
317  output_dir = os.path.dirname(outputfile)
318  try:
319  logging.info("Creating directory %r", output_dir)
320  os.makedirs(output_dir, 0o755)
321  except OSError as err:
322  import errno
323 
324  if err.errno == errno.EEXIST:
325  # somebody already - perhaps concurrently - created that dir.
326  pass
327  else:
328  raise
329 
330  # write output to file
331  logging.verbose("Writing confDb data to %r", outputfile)
332  open(outputfile, "w").write(output)
333  return 0
334 
335 
336 if __name__ == "__main__":
337  retcode = main()
338  sys.exit(retcode)
GaudiKernel.ConfigurableDb.loadConfigurableDb
def loadConfigurableDb()
Definition: ConfigurableDb.py:120
genconfuser._inheritsfrom
def _inheritsfrom(derived, basenames)
Definition: genconfuser.py:34
format
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:119
genconfuser.main
def main()
Definition: genconfuser.py:170
genconfuser.getConfigurableUsers
def getConfigurableUsers(modulename, root, mayNotExist=False)
Definition: genconfuser.py:110
genconfuser.loadConfigurableDb
def loadConfigurableDb(build_dir=None, project_name=None)
Definition: genconfuser.py:53
GaudiKernel.ConfigurableDb
Definition: ConfigurableDb.py:1