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