ATLAS Offline Software
Loading...
Searching...
No Matches
LegacySupport.py
Go to the documentation of this file.
1# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2#
3# Compatibility layer allowing to convert between new (as of 2020) Gaudi Configurable2
4# and old Configurable classes
5
6from AthenaCommon import CfgMgr, CFElements
7from AthenaCommon.AlgSequence import AthSequencer
8from AthenaCommon.Configurable import Configurable, ConfigurableCABehavior
9from AthenaCommon.Logging import logging
10from AthenaConfiguration.ComponentFactory import CompFactory, isComponentAccumulatorCfg
11from AthenaConfiguration.ComponentAccumulator import ConfigurationError
12from AthenaConfiguration.Deduplication import DeduplicationFailed
13
14import GaudiConfig2
15import collections.abc
16
17
18def _indent( indent = ""):
19 """For indendation of debug output"""
20 return indent + " "
21
23 """Is old-style Configurable"""
24 return isinstance(c, Configurable)
25
26
27# Tuple of semantics helpers that need special treatment to retrieve their value
28_semanticsHelpers = (GaudiConfig2.semantics._ListHelper,
29 GaudiConfig2.semantics._DictHelper,
30 GaudiConfig2.semantics._SetHelper)
31
33 """Recursively convert GaudiConfig2 semantics helpers"""
34 if isinstance(value, GaudiConfig2.semantics._ListHelper):
35 return [_conf2HelperToBuiltin(item) for item in value.data]
36 if isinstance(value, GaudiConfig2.semantics._DictHelper):
37 return dict((k,_conf2HelperToBuiltin(v)) for k,v in value.data.items())
38 if isinstance(value, GaudiConfig2.semantics._SetHelper):
39 return set(_conf2HelperToBuiltin(item) for item in value.data)
40 return value
41
42
43def _setProperties( dest, src, indent="" ):
44 """Set properties from src (GaudiConfig2) on dest configurable"""
45
46 _log = logging.getLogger( "_setProperties" )
47
48 for pname, pvalue in src._properties.items():
49 if dest.__class__.__name__ == 'AthSequencer' and pname == 'Members':
50 continue
51
52 propType = src._descriptors[pname].cpp_type
53 if "PrivateToolHandleArray" in propType:
54 setattr( dest, pname,
55 [conf2toConfigurable( tool, indent=_indent( indent ),
56 parent = f"{src.getName()}.{pname}",
57 propType = propType ) for tool in pvalue] )
58 _log.debug( "%sSetting private tool array property %s of %s",
59 indent, pname, dest.name() )
60
61 elif ("PrivateToolHandle" in propType or
62 "GaudiConfig2.Configurables" in propType or
63 "ServiceHandle" in propType):
64
65 _log.debug( "%sSetting property %s of %s",
66 indent, pname, dest.name() )
67 try: # sometimes it is not printable
68 _log.debug("%sComponent: %s", indent, pvalue)
69 except Exception:
70 pass
71 if pvalue is not None:
72 setattr( dest, pname,
73 conf2toConfigurable( pvalue, indent=_indent( indent ),
74 parent = f"{src.getName()}.{pname}",
75 propType = propType ) )
76 else:
77 setattr( dest, pname, pvalue )
78
79 else: # plain data
80 if isinstance(pvalue, _semanticsHelpers):
81 pvalue = _conf2HelperToBuiltin(pvalue)
82 try: # sometimes values are not printable
83 _log.debug( "%sSetting property %s to value %s", indent, pname, pvalue )
84 except Exception:
85 pass
86 setattr( dest, pname, pvalue )
87
88
89def _fetchOldSeq(name=""):
90 with ConfigurableCABehavior(target_state=False):
91 seq = AthSequencer(name)
92 return seq
93
94def _mergeSequences( currentConfigurableSeq, conf2Sequence, _log, indent="" ):
95 """Merge conf2sequence into currentConfigurableSeq"""
96
97 sequence = CFElements.findSubSequence( currentConfigurableSeq, conf2Sequence.name )
98 if not sequence:
99 sequence = _fetchOldSeq( conf2Sequence.name )
100 _setProperties( sequence, conf2Sequence, indent=_indent( indent ) )
101 currentConfigurableSeq += sequence
102 _log.debug( "%sCreated missing AthSequencer %s and added to %s",
103 _indent( indent ), sequence.name(), currentConfigurableSeq.name() )
104
105 for el in conf2Sequence.Members:
106 if el.__class__.__name__ in ["AthSequencer"]:
107 _mergeSequences( sequence, el, _log, _indent( indent ) )
108 elif el.getGaudiType() == "Algorithm":
109 # We will get an error if there are duplicate algs in a sequence, indicating misconfiguration
110 # HLT requires algs to be in many sequences, cannot afford to veto them all here
111 toadd = conf2toConfigurable( el, indent=_indent( indent ))
112 if (
113 toadd is not None
114 # SGInputLoader has to be in AthAlgSeq but doesn't have to be anywhere else
115 and not ( toadd.name()=='SGInputLoader' and sequence.name() not in ['AthAlgSeq','HLTBeginSeq'])
116 ):
117 sequence += toadd
118 _log.debug( "%sAlgorithm %s and added to the sequence %s",
119 _indent( indent ), el.getFullJobOptName(), sequence.name() )
120
121
122def conf2toConfigurable( comp, indent="", parent="", servicesOfThisCA=[], suppressDupes=False, propType="" ):
123 """
124 Method converts from Conf2 ( comp argument ) to old Configurable
125 If the Configurable of the same name exists, the properties merging process is invoked
126 """
127 _log = logging.getLogger( "conf2toConfigurable" )
128
129 if _isOldConfigurable(comp):
130 _log.debug( "%sComponent is already OLD Configurable object %s, no conversion",
131 indent, comp.getName() )
132 return comp
133
134 if isinstance(comp, str):
135 if comp and 'ServiceHandle' not in propType: # warning for non-empty string
136 _log.warning( "%sComponent '%s' in '%s' is of type string, no conversion, "
137 "some properties possibly not set?", indent, comp, parent)
138 return comp
139
140 if comp.getType() == 'AthSequencer':
141 _log.debug( "%sComponent is a sequence %s, attempt to merge",
142 indent, comp.getName())
143 oldsequence = _fetchOldSeq(comp.getName())
144 _mergeSequences(oldsequence, comp, _log, indent)
145 return oldsequence
146
147 _log.debug( "%sConverting from GaudiConfig2 object %s type %s, parent %s",
148 indent, comp.getName(), comp.__class__.__name__ , parent)
149
150 def _alreadyConfigured( comp, parent ):
151 instanceName = comp.getName()
152 for conf in Configurable.allConfigurables.values():
153 conf_name = ''
154 try:
155 conf_name=conf.name()
156 except TypeError:
157 # Is a string
158 conf_name=conf
159
160 if conf_name==instanceName:
161 if conf.getParent() == parent:
162 _log.debug( "%s Matched component: '%s' with parent %s with same from allConfigurables match.",
163 indent, instanceName, parent if parent else "[not set]" )
164 return conf
165 else:
166 _log.debug( "%sComponent: '%s' had parent %s whilst this allConfigurables match had parent %s.",
167 indent, instanceName, parent if parent else "[not set]", conf.getParent() )
168 return None
169
170 def _createConf2Object( name ):
171 typename, instanceName = name.split("/") if "/" in name else (name,name)
172 return CompFactory.getComp( typename.replace("__", "::") )( instanceName )
173
174 def _configurableToConf2( comp, indent="" ):
175 _log.debug( "%sConverting Conf2 to Configurable class %s, type %s", indent, comp.getFullName(), type(comp) )
176 conf2Object = _createConf2Object( comp.getFullName() )
177 _getProperties( comp, conf2Object, _indent( indent ) )
178 return conf2Object
179
180 def _getProperties( src, dest, indent="" ):
181 """Read properties on src and set them on dest (GaudiConfig2) configurable"""
182 for prop, value in src.getProperties().items():
183 _log.debug( "%sDealing with class %s property %s value type %s",
184 indent, src.getFullJobOptName(), prop, type(value) )
185 if "ServiceHandle" in str( type( value ) ):
186 instance = _alreadyConfigured(value, src.getName())
187 if instance:
188 setattr( dest, prop, _configurableToConf2(instance, _indent(indent)) )
189 else:
190 if isinstance(value, _semanticsHelpers):
191 value=value.data
192 setattr( dest, prop, value )
193
194 def _findConfigurableClass( name ):
195 """Find old-style Configurable class for name"""
196 if "::" in name: # cure namespaces
197 name = name.replace("::","__")
198
199 if "<" in name:
200 name=name.replace("<","_")
201 name=name.replace(">","_")
202 name=name.replace(", ","_")
203
204 classObj = getattr( CfgMgr, name )
205
206 if not classObj:
207 raise ConfigurationError(f"CAtoGlobalWrapper could not find the component of type {name}")
208
209 return classObj
210
211 def _areSettingsSame( conf1, conf2, indent="",servicesOfThisCA=[] ):
212 """Are the properties the same between old-style conf1 and new-style conf2 instance?"""
213 from AthenaCommon.AppMgr import ToolSvc
214
215 _log.debug( "%sChecking if settings are the same %s (%s) old(new)",
216 indent, conf1.getFullName(), conf2.getFullJobOptName() )
217
218 if conf1.getType() != conf2.__cpp_type__:
219 raise ConfigurationError("Old/new ({} | {}) cpp types are not the same for ({} | {}) !".format(
220 conf1.getType(), conf2.__cpp_type__,
221 conf1.getFullName(), conf2.getFullJobOptName() ) )
222
223 alreadySetProperties = conf1.getValuedProperties().copy()
224
225 _log.debug( "%sExisting properties: %s", indent, alreadySetProperties )
226 _log.debug( "%sNew properties: %s", indent, conf2._properties )
227
228 for pname, pvalue in conf2._properties.items():
229
230 if _isOldConfigurable( pvalue ):
231 _log.warning( "%sNew configuration object %s property %s has legacy configuration "
232 "components assigned to it %s. Skipping comparison, no guarantees "
233 "about configuration consistency.",
234 indent, conf2.getName(), pname, pvalue.getName() )
235 continue
236
237 propType = conf2._descriptors[pname].cpp_type
238 _log.debug( "%sComparing type: %s for: %s in: %s", indent, propType, pname, conf1.getFullJobOptName() )
239
240 if "PrivateToolHandleArray" in propType:
241 toolDict = {_.getName(): _ for _ in alreadySetProperties[pname]}
242 _log.debug('Private tool properties? %s', toolDict)
243 newCdict = {_.getName() : _ for _ in pvalue}
244 oldCset = set(toolDict); newCset = set(newCdict)
245 _log.debug('Private tool property names? %s %s', oldCset, newCset)
246 if ( not (oldCset == newCset) ):
247 # This is allowed, and happens normally in a number of
248 # cases. Don't warn by default here.
249 _log.debug('%s PrivateToolHandleArray %s of %s does not have the same named components',indent, pname, conf1.getFullJobOptName() )
250 _log.debug('%s Old (conf1) %s for %s',indent, sorted(oldCset), conf1.getFullJobOptName())
251 _log.debug('%s New (conf2) %s for %s',indent, sorted(newCset), conf2.getFullJobOptName())
252 _log.debug('%s Will try to merge them, but this might go wrong!',indent)
253 for oldC in oldCset & newCset:
254 _areSettingsSame( toolDict[oldC], newCdict[oldC], _indent(indent),servicesOfThisCA)
255
256 # And now just the new properties in conf2 (the stuff just in conf1 is already in the objec)
257 for newC in sorted(newCset-oldCset):
258 className = newCdict[newC].getFullJobOptName().split( "/" )[0]
259 _log.debug('%s%s not in old config. Will try to create conf1 instance using '
260 'this className: %s, and merge.',indent, newC, className)
261 configurableClass = _findConfigurableClass( className )
262 # Do not create with existing name, or it will try to get an existing public tool, if available
263 # (and public tools cannot be added to a PrivateToolHandleArray)
264 tmpName = newC + className + str(len(indent))
265 instance = configurableClass( tmpName )
266
267 # Now give it the correct name and fix Configurable DB
268 instance._name = newCdict[newC].name
269 if hasattr(instance, '_jobOptName'):
270 instance._jobOptName = instance._name
271
272 instance.allConfigurables[instance._name] = instance.allConfigurables.pop(tmpName)
273 instance.configurables[instance._name] = instance.configurables.pop(tmpName)
274
275 _setProperties( instance, newCdict[newC], _indent( indent ) )
276 _log.debug('%s will now add %s to array.',indent, instance)
277 conf1 += instance # Makes a copy with a correctly set parent and name
278 del instance
279 alreadySetProperties[pname].append(conf1.getChildren()[-1])
280
281 elif "PublicToolHandleArray" in propType:
282 toolSet = {_.getName() for _ in alreadySetProperties[pname]}
283 _log.debug('Public tool handle array properties? %s %s', toolSet, pvalue)
284 # strings?
285 for newC in pvalue:
286 if isinstance(newC, str):
287 pubtoolclass, pubtoolname = newC.split('/')
288 if pubtoolname not in toolSet:
289 klass = _findConfigurableClass( pubtoolclass )
290 instance = klass(pubtoolname)
291 ToolSvc += instance
292 alreadySetProperties[pname].append(instance)
293 else:
294 _log.warning('Not handling actual Configurable2s for public tool merging yet')
295 raise Exception()
296
297 elif ("PrivateToolHandle" in propType or
298 "GaudiConfig2.Configurables" in propType or
299 "ServiceHandle" in propType):
300 existingVal = getattr(conf1, pname)
301 if isinstance( pvalue, str ):
302 #Try getting the component from the known services:
303 if "ServiceHandle" in propType and pvalue in servicesOfThisCA:
304 _log.debug("%sThe service %s is part of the CA. Consistency checks will be performed when the service is merged")
305 else:
306 # debug only, because we already get a warning from conf2toConfigurable for this
307 _log.debug("%sThe %s '%s' of GaudiConfig2 component %s.%s is a string, "
308 "skipping deeper checks",
309 indent, propType, pvalue, conf2.name, pname)
310 elif pvalue is None:
311 _log.debug("%sThe property value for %s of %s is None. Skipping.", indent, pname, conf2.name )
312 continue
313 elif str(existingVal) == "":
314 className = pvalue.getFullJobOptName().split( "/" )[0]
315 pvalueCompName = pvalue.getFullJobOptName().split( "/" )[1]
316 _log.debug("%sThe existing value for %s of %s is an empty handle. "
317 "Will try to create conf1 instance using this className: %s, and merge.",
318 indent, pname, conf2.name, className )
319 configurableClass = _findConfigurableClass( className )
320 # Do not create with existing name, or it will try to get an existing public tool, if available
321 # (and public tools cannot be added to a PrivateToolHandle)
322 tmpName = pvalueCompName + className + str(len(indent))
323 instance = configurableClass( tmpName )
324 # Now give it the correct name, assign to the conf1 property, and merge
325 instance._name = pvalueCompName
326 if hasattr(instance, '_jobOptName'):
327 instance._jobOptName = instance._name
328
329 instance.allConfigurables[instance._name] = instance.allConfigurables.pop(tmpName)
330 instance.configurables[instance._name] = instance.configurables.pop(tmpName)
331
332 setattr(conf1, pname, instance)
333 existingVal = getattr(conf1, pname)
334 _areSettingsSame( existingVal, pvalue, indent,servicesOfThisCA)
335 else:
336 _log.debug( "%sSome kind of handle and, object type %s existing %s",
337 indent, type(pvalue), type(existingVal) )
338 _areSettingsSame( existingVal, pvalue, indent,servicesOfThisCA)
339 else:
340 if isinstance(pvalue, _semanticsHelpers):
341 pvalue = _conf2HelperToBuiltin(pvalue)
342
343 if pname not in alreadySetProperties:
344 _log.debug( "%sAdding property: %s for %s", indent, pname, conf2.getName() )
345 try:
346 setattr(conf1, pname, pvalue)
347 except AttributeError:
348 _log.info("%sCould not set attribute. Type of conf1 %s.",indent, type(conf1) )
349 raise
350
351 elif alreadySetProperties[pname] != pvalue:
352 # Old configuration writes some properties differently e.g. ConditionStore+TileBadChannels
353 # instead of just TileBadChannels. So check if this isn't a false positive before continuing.
354 merge = True
355
356 # Could be strings e.g. alreadySetProperties[pname]==RPCCablingDbTool
357 # and pvalue == RPCCablingDbTool/RPCCablingDbTool
358 if (isinstance(pvalue, str) and isinstance(alreadySetProperties[pname], str)):
359 if ('/' in pvalue \
360 and pvalue.split('/')[-1] == alreadySetProperties[pname]):
361 # Okay. so they probably are actually the same. Can't check type.
362 merge = False
363 _log.warning( "%sProperties here are strings and not exactly the same. "
364 "ASSUMING they match types but we cannot check. %s for %s",
365 indent, pname, conf2.getName() )
366 try:
367 if ('+' in alreadySetProperties[pname].toStringProperty() and
368 alreadySetProperties[pname].toStringProperty().split('+')[-1] == pvalue):
369 # Okay. so they ARE actually the same
370 merge = False
371 except AttributeError :
372 # This is fine - it just means it's not e.g. a DataHandle and doesn't have toStringProperty
373 pass
374
375 # Okay, not the same ... let's merge
376 if merge:
377 _log.debug( "%sMerging property: %s for new config: %s", indent, pname, conf2.getName() )
378 # create surrogate
379 clone = conf2.getInstance("Clone")
380 setattr(clone, pname, alreadySetProperties[pname])
381 try:
382 updatedPropValue = _conf2HelperToBuiltin( conf2._descriptors[pname].semantics.merge(
383 getattr(conf2, pname), getattr(clone, pname)) )
384 except (TypeError, ValueError):
385 err_message = f"Failed merging new config value ({getattr(conf2, pname)}) and old config value ({getattr(clone, pname)}) for the ({pname}) property of {conf1.getFullJobOptName() } ({conf2.getFullJobOptName()}) old (new)."
386 _log.fatal( err_message )
387 raise ConfigurationError(err_message)
388
389 _log.debug("existingConfigurable.name: %s, pname: %s, updatedPropValue: %s",
390 conf1.name(), pname, updatedPropValue )
391
392 setattr(conf1, pname, updatedPropValue)
393 del clone
394 _log.debug("%sInvoked GaudiConf2 semantics to merge the %s and the %s to %s "
395 "for property %s of %s",
396 indent, alreadySetProperties[pname], pvalue, pname,
397 updatedPropValue, existingConfigurable.getFullName())
398
399 _log.debug( "%sConf2 Full name: %s ", indent, comp.getFullJobOptName() )
400 existingConfigurable = _alreadyConfigured( comp, parent )
401
402 if existingConfigurable: # if configurable exists we try to merge with it
403 _log.debug( "%sPre-existing configurable %s was found, checking if has the same properties",
404 indent, existingConfigurable.getFullJobOptName() )
405 _areSettingsSame( existingConfigurable, comp, indent, servicesOfThisCA )
406 _log.debug( "%sPre-existing configurable %s was found to have the same properties",
407 indent, comp.name )
408 instance = existingConfigurable if not suppressDupes else None
409
410 else: # create new configurable
411 _log.debug( "%sExisting Conf1 not found. Creating component configurable %s",
412 indent, comp.getFullJobOptName() )
413 configurableClass = _findConfigurableClass( comp.getFullJobOptName().split( "/" )[0] )
414 instance = configurableClass( comp.name )
415 _setProperties( instance, comp, _indent( indent ) )
416
417 return instance
418
419
420def CAtoGlobalWrapper(cfgFunc, flags, **kwargs):
421 """Execute the cfgFunc CA with the given flags and arguments and run appendCAtoAthena.
422 Return the result of cfgFunc."""
423 if isComponentAccumulatorCfg():
424 raise RuntimeError("CAtoGlobalWrapper should not be called in pure CA config")
425
426 if not callable(cfgFunc):
427 raise TypeError("CAtoGlobalWrapper must be called with a configuration-function as parameter")
428
429 with ConfigurableCABehavior():
430 result = cfgFunc(flags, **kwargs)
431 if isinstance(result, tuple):
432 ca = result[0]
433 else:
434 ca = result
435
437 return result
438
440 from AthenaCommon.AppMgr import (ServiceMgr, ToolSvc, theApp,
441 athCondSeq, athOutSeq, athAlgSeq)
442 _log = logging.getLogger( "conf2toConfigurable" )
443 _log.debug( "Merging ComponentAccumulator into global configuration" )
444
445
446 servicesOfThisCA=[svc.getFullJobOptName() for svc in ca.getServices()]
447
448 if len( ca.getPublicTools() ) != 0:
449 for comp in ca.getPublicTools():
450 instance = conf2toConfigurable( comp, indent=" ", parent="ToolSvc", servicesOfThisCA=servicesOfThisCA )
451 if instance not in ToolSvc:
452 ToolSvc += instance
453
454 if len(ca.getServices()) != 0:
455 for comp in ca.getServices():
456 instance = conf2toConfigurable( comp, indent=" ", servicesOfThisCA=servicesOfThisCA )
457 if instance not in ServiceMgr:
458 ServiceMgr += instance
459 for svcName in ca._servicesToCreate:
460 if svcName not in theApp.CreateSvc:
461 theApp.CreateSvc += [svcName]
462
463 if len(ca._conditionsAlgs) != 0:
464 for comp in ca._conditionsAlgs:
465 instance = conf2toConfigurable( comp, indent=" ", servicesOfThisCA=servicesOfThisCA )
466 if instance not in athCondSeq:
467 athCondSeq += instance
468
469 if len( ca.getAppProps() ) != 0:
470 for propName, propValue in ca.getAppProps().items():
471 # Same logic as in ComponentAccumulator.setAppProperty()
472 if not hasattr(theApp, propName):
473 setattr(theApp, propName, propValue)
474 else:
475 origPropValue = getattr(theApp, propName)
476 if origPropValue == propValue:
477 _log.info("ApplicationMgr property '%s' already set to '%s'.", propName, propValue)
478 elif isinstance(origPropValue, collections.abc.Sequence) and not isinstance(origPropValue, str):
479 propValue = origPropValue + [el for el in propValue if el not in origPropValue]
480 _log.info("ApplicationMgr property '%s' already set to '%s'. Overwriting with %s",
481 propName, origPropValue, propValue)
482 setattr(theApp, propName, propValue)
483 else:
484 raise DeduplicationFailed(f"ApplicationMgr property {propName} set twice: "
485 "{origPropValue} and {propValue}")
486
487 preconfigured = [athCondSeq,athOutSeq,athAlgSeq]
488
489 for seq in ca._allSequences:
490 merged = False
491 for pre in preconfigured:
492 if seq.getName() == pre.getName():
493 _log.debug( "%sfound sequence %s to have the same name as predefined %s",
494 _indent(), seq.getName(), pre.getName() )
495 _mergeSequences( pre, seq, _log )
496 merged = True
497 break
498 if CFElements.findSubSequence( pre, seq.name ):
499 _log.debug( "%sfound sequence %s in predefined %s",
500 _indent(), seq.getName(), pre.getName() )
501 _mergeSequences( pre, seq, _log )
502 merged = True
503 break
504
505 if not merged:
506 _log.debug( "%snot found sequence %s merging it to AthAlgSeq", _indent(), seq.name )
507 _mergeSequences( athAlgSeq, seq, _log )
508
509 ca.wasMerged()
510 _log.debug( "Merging of CA to global done" )
ClassName: AthSequencer.
STL class.
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
conf2toConfigurable(comp, indent="", parent="", servicesOfThisCA=[], suppressDupes=False, propType="")
_mergeSequences(currentConfigurableSeq, conf2Sequence, _log, indent="")
_setProperties(dest, src, indent="")
CAtoGlobalWrapper(cfgFunc, flags, **kwargs)