ATLAS Offline Software
Loading...
Searching...
No Matches
Tools/PyUtils/python/Dso.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3
6
7__author__ = "Sebastien Binet"
8
9__all__ = ['DsoDb']
10
11import sys
12import os
13import re
14
15def _libName(lib):
16 import platform
17 if platform.system() == "Linux":
18 if lib[:3] != "lib": lib = "lib"+lib
19 if lib[-3:] != ".so": lib = lib+".so"
20 return lib
21
22
23_aliases = {
24 'ElementLinkInt_p1' : 'ElementLink_p1<unsigned int>',
25 'basic_string<char>' : 'string',
26 'std::basic_string<char>' : 'string',
27 'vector<basic_string<char> >' : 'vector<string>',
28
29 'INavigable4MomentumCollection' : 'DataVector<INavigable4Momentum>',
30 'IParticleContainer' : 'DataVector<IParticle>',
31 }
32_typedefs = {
33
34 'INavigable4MomentumCollection' : 'DataVector<INavigable4Momentum>',
35 'IParticleContainer' : 'DataVector<IParticle>',
36 }
37
38_cpp_builtins = (
39 'char', 'unsigned char', 'signed char',
40 'signed',
41 'short int', 'short signed', 'short signed int',
42 'short', 'unsigned short', 'signed short',
43 'int', 'unsigned int',
44
45 'long int',
46 'long signed int',
47 'signed long int',
48
49 'long',
50 'long signed', 'signed long',
51 'unsigned long',
52 'unsigned long int',
53 'long unsigned int',
54
55 'long long',
56 'long long int',
57 'unsigned long long',
58 'longlong',
59
60 # no clue from where this one comes from, who's requesting it nor who
61 # got the alien naming scheme idea...
62 'ulonglong',
63
64 'float',
65 'double',
66 'long double',
67 'bool',
68 )
69
70_is_stl_sequence = re.compile (r'std::(?P<ContType>.*?)'
71 r'<(?P<TemplateArg>.*?)'
72 r',\s*?std::allocator<\2> >')
73_is_stl_mapping = re.compile (r'std::map<'
74 r'(?P<TemplateArg1>.*?),\s*?'
75 r'(?P<TemplateArg2>.*?)'
76 r',\s*?std::allocator<\2> >')
77
78
79
80
81
82def _get_native_libname(libname):
83 """ return the OS-native name from an OS-indenpendent one """
84 plat = sys.platform
85 if plat.count('linux')>0:
86 lib_prefix,lib_suffix = 'lib', '.so'
87 elif plat == 'win32':
88 lib_prefix,lib_suffix = '', '.dll'
89 elif plat == 'darwin':
90 lib_prefix,lib_suffix = 'lib','.dylib'
91 else:
92 raise RuntimeError ("sorry platform [%s] is not (yet?) supported"%plat)
93 _sys_libname = libname
94 if not _sys_libname.startswith (lib_prefix):
95 _sys_libname = ''.join([lib_prefix,_sys_libname])
96 if not _sys_libname.endswith (lib_suffix):
97 _sys_libname = ''.join([_sys_libname, lib_suffix])
98 return _sys_libname
99
100def load_library (libname):
101 """
102 Helper method to load a library by its natural name, not the OS-native name.
103 But if the OS-native name is given, it is safely handled too.
104 usage:
105 >>> load_library ('AthenaServices')
106 >>> load_library ('AthenaServicesDict')
107 """
108 _sys_libname = _get_native_libname(libname)
109 import ctypes
110 return ctypes.cdll.LoadLibrary (_sys_libname)
111
112def find_library(libname):
113 """
114 Helper function to find the (full)path to a library given its natural name.
115 @return None on failure
116
117 usage:
118 >>> find_library('AthenaServices')
119 '/afs/cern.ch/.../AtlasCore/[release]/InstallArea/.../libAthenaServices.so
120 """
121 import os
122
128 _sys_libname = _get_native_libname(libname)
129 # FIXME: REALLY not portable...
130 if os.name != 'posix':
131 raise RuntimeError('sorry OS [%s] is not supported' % os.name)
132
133 if 'LD_LIBRARY_PATH' in os.environ:
134 for d in os.environ['LD_LIBRARY_PATH'].split(os.pathsep):
135 lib = os.path.join(d, _sys_libname)
136 if os.path.exists(lib):
137 return lib
138 return
139
140
141def _is_rootcint_dict (libname):
142 """helper function to reject rootcint libraries entries from rootmap
143 files (which appeared w/ ROOT v21/22)
144 It seems all of them (and on all platforms) are named like:
145 vector<char>: vector.dll
146 """
147 if libname == ".dll": # pathological case...
148 return False
149 pat = re.compile(r'\w*?.dll')
150 return not (libname.startswith("lib")) and \
151 not (pat.match (libname) is None)
152
153class CxxDsoDb(object):
154 """
155 The repository of 'rootmap' files (location, content,...)
156 """
157 def __init__(self):
158 import cppyy # noqa: F401
159 # import root
160 import PyUtils.RootUtils as ru
161 ROOT = ru.import_root()
162 self._cxx = ROOT.Ath.DsoDb.instance()
163
164 def _to_py(self, cxx):
165 dd = {}
166 kk = self._cxx.py_keys_from(cxx)
167 vv = self._cxx.py_vals_from(cxx)
168 for i in range(kk.size()):
169 dd[kk[i]] = list(vv[i])
170 return dd
171
172 @property
173 def db(self):
174 return self._to_py(self._cxx.db())
175
176 @property
177 def pf(self):
178 return self._to_py(self._cxx.pf())
179
180 def has_type(self, typename):
181 return self._cxx.has_type(typename)
182
183 def load_type(self, typename):
184 return self._cxx.load_type(typename)
185
186 def capabilities(self, libname):
187 return list(self._cxx.capabilities(libname))
188
189 def duplicates(self, libname, pedantic=False):
190 return self._to_py(self._cxx.duplicates(libname, pedantic))
191
192 def dict_duplicates(self, pedantic=False):
193 return self._to_py(self._cxx.dict_duplicates(pedantic))
194
195 dictDuplicates = dict_duplicates
196
197 def pf_duplicates(self, pedantic=False):
198 return self._to_py(self._cxx.pf_duplicates(pedantic))
199
200 pfDuplicates = pf_duplicates
201
202 def libs(self, detailed=False):
203 return list(self._cxx.libs(detailed))
204
205 def content(self, pedantic):
206 return self._to_py(self._cxx.content(pedantic))
207
208 @property
209 def dso_files(self):
210 return list(self._cxx.dso_files())
211
212 @property
213 def dsoFiles(self):
214 return self.dso_files
215
216def _to_rootmap_name(typename):
217 """
218 helper method to massage a typename into something understandable
219 by the rootmap files
220 """
221 typename = typename.replace(', ',',')
222 # first the easy case: builtins
223 if typename in _cpp_builtins:
224 return typename
225 # known missing aliases ?
226 if typename in _aliases.keys():
227 t = _aliases[typename]
228 return _to_rootmap_name(t)
229 # handle default template arguments of STL sequences
230 if _is_stl_sequence.match(typename):
231 # rootmap files do not contain the default template arguments
232 # for STL containers... consistency, again.
233 _m = _is_stl_sequence.match(typename)
234 _m_type = _m.group('TemplateArg')
235 # handle the dreaded 'std::Bla<Foo<d> >
236 _m_type = _to_rootmap_name(_m_type.strip())
237 if _m_type.endswith('>'):
238 _m_type += ' '
239 typename = 'std::%s<%s>' % (_m.group('ContType'),
240 _m_type)
241 # need to massage a bit the typename to match ROOT naming convention
242 typename = typename.replace('std::basic_string<char> ',
243 'string ')
244 typename = typename.replace('std::basic_string<char>',
245 'string')
246 typename = typename.replace('std::', '')
247 typename = typename.replace('> >', '>->')
248 typename = typename.replace(' >', '>')
249 typename = typename.replace('>->', '> >')
250 return typename
251
252def _to_rflx_name (typename):
253 """helper method to massage a typename into something understandable
254 by reflex (which doesn't understand the same thing than rootmaps).
255 """
256 typename = typename.replace(', ',',')
257 # first the easy case: builtins
258 if typename in _cpp_builtins:
259 return typename
260 # known missing typedefs ?
261 if typename in _typedefs.keys():
262 t = _typedefs[typename]
263 return _to_rflx_name(t)
264 # handle default template arguments of STL sequences
265 if _is_stl_sequence.match(typename):
266 # rootmap files do not contain the default template arguments
267 # for STL containers... consistency, again.
268 _m = _is_stl_sequence.match (typename)
269 _m_type = _m.group('TemplateArg')
270 # handle the dreaded 'std::Bla<Foo<d> >
271 _m_type = _to_rflx_name (_m_type.strip())
272 if _m_type.endswith('>'):
273 _m_type += ' '
274 typename = 'std::%s<%s>' % (_m.group('ContType'), _m_type)
275 typename = typename.replace('std::string>',
276 'std::basic_string<char> >')
277 typename = typename.replace('std::string',
278 'std::basic_string<char>')
279 return typename
280
281class PyDsoDb( object ):
282 """
283 The repository of 'rootmap' files (location, content,...)
284 """
285 RootMap = "rootmap"
286 DsoMap = "dsomap"
287 PluginNamespace = "__pf__"
288
289 def __init__(self, name = "DsoDb"):
290 object.__init__(self)
291 self.name = name
292 self.db = { } # repository of components
293 self.pf = { } # repository of known components to the plugin svc
294
295 import PyUtils.Logging as _L
296 self.msg = _L.logging.getLogger('DsoDb')
297
298 self.dsoPath = os.environ['LD_LIBRARY_PATH']
299 self.__buildRepository()
300 return
301
302 def __buildRepository(self):
303 msg = self.msg
304 self.dsoFiles = set()
305 dsoPath = [p for p in self.dsoPath.split( os.pathsep )
306 if not p.startswith(os.environ['ROOTSYS'])]
307 for path in dsoPath:
308 if not os.path.exists(path): continue
309 dir_content = None
310 try:
311 dir_content = os.listdir(path)
312 except Exception:
313 # try again...
314 try:
315 dir_content = os.listdir(path)
316 except Exception as err:
317 msg.warning("caught:\n%s", err)
318 if dir_content is None:
319 msg.warning("could not run os.listdir on [%s]", path)
320 dir_content = []
321 dsoFiles = [ f for f in dir_content
322 if f.endswith(self.RootMap) ]
323 for dsoFile in dsoFiles:
324 dsoFile = os.path.join( path, dsoFile )
325 if os.path.exists(dsoFile):
326 line_nbr = -1
327 self.dsoFiles.add(dsoFile)
328 for line in open(dsoFile, 'r'):
329 line_nbr += 1
330 line = line.strip()
331 if len(line) <= 0 or line[0] == "#":
332 continue
333 line = line.split()
334 # Note that as of LCG-55, rootmaps have the following
335 # format: 'symbol': libDict.so [listOfLinkedLibs.so...]
336 # we are only interested in libDict.so...
337 try:
338 dsoKey, libName = line[0], line[1]
339 except Exception as err:
340 msg.warning(
341 'could not parse %s:%i', dsoFile, line_nbr
342 )
343 msg.warning(
344 '(some) reflex-dicts may fail to be auto-loaded'
345 )
346 msg.warning(err)
347 continue
348 dsoKey = dsoKey\
349 .replace("Library.", "")\
350 .replace( ":", "" )\
351 .replace( "@", ":" )\
352 .replace( "-", " " )
353 if dsoKey.startswith( self.PluginNamespace ):
354 db = self.pf
355 else:
356 db = self.db
357 if dsoKey not in db: db[dsoKey] = list()
358 if _is_rootcint_dict (libName):
359 #print "## discarding [%s]..." % libName
360 continue
361 libName = os.path.join(path, _libName(libName))
362 db[dsoKey].append(libName)
363 pass # loop over dso-lines
364 pass # loop over dsoFiles
365 pass # iter over dsoPath
366 return
367
368 def __str__(self):
369 s = os.linesep.join( [
370 "+--- %s ---" % self.name,
371 "|nbr of lib components: %i" % len(self.db.keys()),
372 "|nbr of pf components: %i" % len(self.pf.keys()),
373 "|nbr of dso files: %i" % len(self.dsoFiles),
374 "|nbr of known libs: %i" % len(self.libs()),
375 "+-------------------------"
376 ] )
377
378 return s
379
380 def __dups(self, db, pedantic):
381 dups = {}
382 for k in db.keys():
383 if len(db[k]) == 1: continue
384 if pedantic: libs = db[k]
385 else:
386 baseLibs = set()
387 libs = []
388 for lib in db[k]:
389 if os.path.basename(lib) not in baseLibs:
390 libs.append(lib)
391 baseLibs.add(os.path.basename(lib))
392 pass
393 pass
394 if len(libs) > 1:
395 dups[k] = [ lib for lib in libs ]
396 return dups
397
398 def duplicates(self, libName, pedantic = False):
399 caps = self.capabilities(libName)
400 dups = {}
401 for dupDb in [ self.dictDuplicates(pedantic),
402 self.pfDuplicates(pedantic) ]:
403 for k in dupDb:
404 if k in caps:
405 if k not in dups: dups[k] = []
406 dups[k] += [ lib for lib in dupDb[k]
407 if libName not in os.path.basename(lib) ]
408 dups.keys().sort()
409 for k in dups.keys():
410 dups[k].sort()
411 return dups
412
413 def dictDuplicates(self, pedantic = False):
414 return self.__dups(self.db, pedantic)
415
416 def pfDuplicates(self, pedantic = False):
417 return self.__dups(self.pf, pedantic)
418
419 def capabilities(self, libName):
420 libName = _libName(libName)
421 caps = set()
422 for db in [self.db, self.pf]:
423 for k in db.keys():
424 if libName in [ os.path.basename(lib) for lib in db[k] ]:
425 caps.add( k )
426 caps = [ cap for cap in caps ]
427 caps.sort()
428 if len(caps) == 0:
429 print ("::: ERROR: No such library [%s] in dsoDb !!" % libName)
430 raise ValueError ("")
431 return caps
432
433 def libs(self, detailedDump = False):
434 if detailedDump: fct = lambda x : x
435 else: fct = os.path.basename
436 libs = set()
437 for db in [self.pf, self.db]:
438 for k in db.keys():
439 for lib in db[k]:
440 libs.add(fct(lib))
441 libs = [ lib for lib in libs ]
442 libs.sort()
443 return libs
444
445 def content(self, pedantic):
446 d = {}
447 for db in [self.pf, self.db]:
448 for k in db.keys():
449 if pedantic: libs = db[k]
450 else:
451 baseLibs = set()
452 libs = []
453 for lib in db[k]:
454 if os.path.basename(lib) not in baseLibs:
455 libs.append(lib)
456 baseLibs.add(os.path.basename(lib))
457 pass
458 pass
459 d[k] = [ lib for lib in libs ]
460 return d
461
462 def _to_rootmap_name(self, typename):
463 """
464 helper method to massage a typename into something understandable
465 by the rootmap files
466 """
467 return _to_rootmap_name(typename)
468
469 def _to_rflx_name (self, typename):
470 """helper method to massage a typename into something understandable
471 by reflex (which doesn't understand the same thing than rootmaps).
472 """
473 return _to_rflx_name(typename)
474
475DsoDb = CxxDsoDb
476#DsoDb = PyDsoDb
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
STL class.
bool add(const std::string &hname, TKey *tobj)
Definition fastadd.cxx:55
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177