6 from typing
import Dict, List, Set, Tuple, cast
13 from AthenaConfiguration.iconfTool.models.element
import (
17 from AthenaConfiguration.iconfTool.models.structure
import ComponentsStructure
19 logger = logging.getLogger(
"confTool")
20 logger.setLevel(level=logging.INFO)
21 logger.addHandler(logging.FileHandler(
"confTool-last-run.log", mode=
'w'))
23 componentRenamingDict={}
25 baseParser = argparse.ArgumentParser()
26 baseParser.add_argument(
30 help=
"Include only components matching (regex) this string",
33 baseParser.add_argument(
37 help=
"Exclude components matching (regex) this string",
40 baseParser.add_argument(
42 help=
"Ignore differences in e.g. outputlevel",
45 baseParser.add_argument(
61 help=
"Ignore properties",
63 baseParser.add_argument(
66 help=
"Pass comps You want to rename as OldName=NewName.",
69 baseParser.add_argument(
71 help=
"Pass the file containing remaps",
74 baseParser.add_argument(
76 help=
"Ignore values that are identical to the c++ defaults. Use it only when when the same release is setup as the one used to generate the config.",
80 baseParser.add_argument(
81 "--ignoreDefaultNamedComps",
82 help=
"""Ignores default handles that have full type specified. That is, if the setting is actually: Tool/A and the default value was just A, the Tool/A is assumed to be default and eliminated.
83 Beware that there is a caveat, the ignored class name may be actually different from the default (there is no way to check that in python).""",
88 baseParser.add_argument(
89 "--shortenDefaultComponents",
90 help=
"Automatically shorten component names that have a default name i.e. ToolX/ToolX to ToolX. It helps comparing Run2 & Run3 configurations where these are handled differently",
94 baseParser.add_argument(
96 help=
"Do not load properties other than those referring to other components",
100 baseParser.add_argument(
102 help=
"Follow to related components up to given recursion depth (3)",
107 baseParser.add_argument(
109 help=
"Enable tool debugging messages",
114 return [item
for elem
in l
for item
in elem]
if l
else []
118 """Updates the dictionary with (potentially) component name -> component type"""
121 s = ast.literal_eval(
str(value))
123 if isinstance(s, list):
129 if isinstance(value,str):
130 slash_startend = value.startswith(
"/")
or value.endswith(
"/")
131 json_dict = value.startswith(
"{")
and value.endswith(
"}")
132 if value.count(
"/")==1
and not parsable
and not slash_startend
and not json_dict:
133 comp = value.split(
"/")
136 dict_to_update[f
'{comp_name}.{comp[1]}'] = comp[0]
137 dict_to_update[f
'{comp[1]}'] = comp[0]
138 logger.debug(
"Parsing %s, found type of %s.%s to be %s", value, comp_name, comp[1], comp[0])
140 logger.debug(
"What is typeless comp? %s", value)
141 if isinstance(value, dict):
142 for v
in value.values():
148 for (comp_name, comp_settings)
in conf.items():
160 def eligible(component):
161 exclude = any(re.match(s, component)
for s
in compsToExclude)
162 if (component
in compsToFollow
or component.removeprefix(
"ToolSvc.")
in compsToFollow)
and not (exclude
or component
in args.ignore):
163 logger.debug(
"Considering this component: %s because some other one depends on it", component)
165 include = any(re.match(s, component)
for s
in compsToInclude)
166 if args.includeComps
and args.excludeComps:
167 return include
and not exclude
168 elif args.includeComps:
170 elif args.excludeComps:
173 for (comp_name, comp_attributes)
in dic.items():
174 if eligible(comp_name):
175 conf[comp_name] = comp_attributes
179 logger.debug(
"Following up for types included in here %s whole set of components to follow %s ", types, compsToFollow)
180 compsToFollow += types.keys()
181 logger.debug(
"Included component %s", comp_name)
183 logger.debug(
"Ignored component %s", comp_name)
189 def remove_irrelevant(val_dict):
191 { key: val
for key, val
in val_dict.items()
if key
not in args.ignore }
192 if isinstance(val_dict, dict)
196 for (key, value)
in dic.items():
197 conf[key] = remove_irrelevant(value)
202 if args.renameCompsFile:
203 with open( args.renameCompsFile,
"r")
as refile:
205 if not (line.startswith(
"#")
or line.isspace() ):
206 compsToRename.append( line.rstrip(
'\n') )
207 global componentRenamingDict
208 componentRenamingDict.update({
210 for old_name, new_name
in [
211 [e.strip()
for e
in element.split(
"=")]
for element
in compsToRename
214 for f,t
in componentRenamingDict.items():
215 logger.info(
"Renaming from: %s to %s", f, t)
217 def rename_comps(comp_name):
218 """Renames component if it is in the dict or, when name fragment is in the dict
219 The later is for cases like: ToolSvc.ToolA.X.Y is renamed to ToolSvc.ToolB.X.Y
221 logger.debug(
"Trying renaming on, %s", comp_name)
222 for k,v
in componentRenamingDict.items():
229 return comp_name.replace(old, f
".{v}.")
232 if comp_name.startswith(old):
233 return comp_name.replace(old, f
"{v}.")
237 if comp_name.endswith(old):
238 return comp_name.replace(old, f
".{k}")
242 for (key, value)
in dic.items():
243 renamed = rename_comps(key)
245 logger.debug(
"Renamed comp %s to %s", key, renamed)
246 conf[renamed] = value
251 def drop_defaults(component_name, val_dict):
253 component_name_last_part = component_name.split(
".")[-1]
254 component_type = known.get(component_name, known.get(component_name_last_part, component_name_last_part))
257 from AthenaConfiguration.ComponentFactory
import CompFactory
258 comp_cls = CompFactory.getComp(component_type)
259 logger.debug(
"Loaded the configuration class %s/%s for defaults elimination", component_type, component_name)
261 logger.debug(
"Could not find the configuration class %s/%s, no defaults for it can be eliminated", component_type, component_name)
265 for k,v
in val_dict.items():
266 if not hasattr(comp_cls,
'_descriptors'):
267 logger.debug(
'No \'_descriptors\' attibute for %s', comp_cls)
269 if k
not in comp_cls._descriptors:
272 default =
str(comp_cls._descriptors[k].default)
275 default.replace(
"StoreGateSvc+",
"") == sv.replace(
"StoreGateSvc+",
"")
or
276 default.replace(
"ConditionStore+",
"") == sv.replace(
"ConditionStore+",
"")):
277 logger.debug(
"Dropped default value \'%s\' of property %s in %s because the default is \'%s\'", sv, k, component_name,
str(default))
278 elif args.ignoreDefaultNamedComps
and isinstance(v, str)
and sv.endswith(f
"/{default}"):
279 logger.debug(
"Dropped speculatively value %s of property %s in %s because the default it ends with %s", sv, k, component_name,
str(default))
282 logger.debug(
"Keep value %s of property %s in %s because it is different from default %s",
str(v),
str(k), component_name,
str(comp_cls._descriptors[k].default))
286 for (comp_name, comp_settings)
in allconf.items():
287 remaining = drop_defaults(comp_name, comp_settings)
288 if len(remaining) != 0:
289 conf[comp_name] = remaining
298 value = ast.literal_eval(
str(value))
302 if isinstance(value, str):
303 svalue = value.split(
"/")
304 if len(svalue) == 2
and svalue[0] == svalue[1]:
305 logger.debug(
"Shortened %s", svalue)
307 if isinstance(value, list):
308 return [shorten(el)
for el
in value]
309 if isinstance(value, dict):
310 return shorten_defaults(value)
314 def shorten_defaults(val_dict):
315 if isinstance(val_dict, dict):
316 return { key: shorten(val)
for key,val
in val_dict.items() }
318 for (key, value)
in dic.items():
319 conf[key] = shorten_defaults(value)
323 """Returns a list of (component,class) if value stores reference to other components
324 value - the value to check
325 compname - full component name
326 conf - complete config dict
329 def _getSvcClass(instance):
330 """Find instance in service lists to get class.
331 Keeps a cache of the service classes in the svcCache default value.
332 That's fine, unless we are dealing with more than one conf in the program.
333 In that case, initialise svcCache to {} and specify in the caller."""
335 props = conf.get(
'ApplicationMgr',{
"":
None})
336 if isinstance(props,dict):
337 for prop,val
in props.items():
340 val = ast.literal_eval(
str(val))
343 if isinstance(val,list):
345 if isinstance(v,str):
348 if svcCache.setdefault(vv[1], vv[0]) != vv[0]:
349 svcCache[vv[1]] =
None
350 return svcCache.get(instance)
353 value = ast.literal_eval(
str(value))
357 if isinstance(value, str):
358 ctype_name = value.split(
'/')
359 cls = ctype_name[0]
if len(ctype_name) == 2
else None
360 instance = ctype_name[-1]
363 if compname
and f
"{compname}.{instance}" in conf:
364 ref = f
"{compname}.{instance}"
365 elif f
"ToolSvc.{instance}" in conf:
366 ref = f
"ToolSvc.{instance}"
367 elif cls
is not None or instance
in conf:
370 cls = _getSvcClass(instance)
374 elif isinstance(value, list):
375 refs = [
isReference(el, compname, conf)
for el
in value]
378 [flattened.extend(el)
for el
in refs
if el]
385 for (name, properties)
in conf.items():
387 if not isinstance(properties, dict):
388 updated[name] = properties
390 for property_name, value
in properties.items():
391 if isReference( value, name, conf)
or property_name ==
'Members':
392 updated[name][property_name] = value
396 """loads config file into a dictionary, supports several modifications of the input switched on via additional arguments
397 Supports reading: Pickled file with the CA or properties & JSON
400 print(
"Debugging info from reading ", fname,
" in ", logger.handlers[0].baseFilename)
401 logger.setLevel(logging.DEBUG)
404 if fname.endswith(
".pkl"):
405 with open(fname,
"rb")
as input_file:
407 cfg = pickle.load(input_file)
408 logger.info(
"... Read %s from %s", cfg.__class__.__name__, fname)
409 from AthenaConfiguration.ComponentAccumulator
import ComponentAccumulator
410 if isinstance(cfg, ComponentAccumulator):
411 props = cfg.gatherProps()
415 for comp, name, value
in jos_props:
416 to_json.setdefault(comp, {})[name] = value
417 to_json[comp][name] = value
419 conf[
'ApplicationMgr'] = props[0]
420 conf[
'MessageSvc'] = props[1]
423 cfg, (collections.defaultdict, dict)
426 conf.update(pickle.load(input_file))
428 elif isinstance(cfg, (collections.Sequence)):
431 logger.info(
"... Read %d items from python pickle file: %s", len(conf), fname)
433 elif fname.endswith(
".json"):
435 def __keepPlainStrings(element):
436 if isinstance(element, str):
438 if isinstance(element, list):
439 return [__keepPlainStrings(x)
for x
in element]
440 if isinstance(element, dict):
442 __keepPlainStrings(key): __keepPlainStrings(value)
443 for key, value
in element.items()
447 with open(fname,
"r")
as input_file:
448 cfg = json.load(input_file, object_hook=__keepPlainStrings)
457 if 'properties' in conf:
458 conf = conf[
'properties']
460 logger.info(
"... Read %d items from json file: %s", len(conf), fname)
463 sys.exit(
"File format not supported.")
466 sys.exit(
"Unable to load %s file" % fname)
468 if args.includeComps
or args.excludeComps
or args.includeClasses
or args.excludeClasses:
469 logger.info(f
"include/exclude comps like {args.includeComps}/{args.excludeComps}")
472 if args.ignoreIrrelevant:
475 if args.renameComps
or args.renameCompsFile:
478 if args.ignoreDefaults:
482 if args.shortenDefaultComponents:
485 if args.skipProperties:
490 def __init__(self, file_path: str, args, checked_elements=
set()) ->
None:
491 self.file_path: str = file_path
492 self.checked_elements: Set[str] = checked_elements
496 logger.info(f
"Loading {self.file_path}")
502 structure = ComponentsStructure(data, self.checked_elements)
515 checked_elements: Set[str],
518 file_path, checked_elements
521 diff_file_path, checked_elements
524 def get_data(self) -> Tuple[ComponentsStructure, ComponentsStructure]:
525 structure = self.main_loader.load_structure()
526 diff_structure = self.diff_loader.load_structure()
528 return structure, diff_structure
530 def equal(self, first: Element, second: Element) -> bool:
532 first.get_name() == second.get_name()
533 and first.x_pos == second.x_pos
538 self, structure: List[Element], diff_structure: List[Element]
541 while i < len(structure)
and j < len(diff_structure):
542 if self.
equal(structure[i], diff_structure[j]):
543 if isinstance(structure[i], GroupingElement):
545 cast(GroupingElement, structure[i]).children,
546 cast(GroupingElement, diff_structure[j]).children,
553 for tmp_j
in range(j, len(diff_structure)):
554 if self.
equal(structure[i], diff_structure[tmp_j]):
555 for marking_j
in range(j, tmp_j):
556 diff_structure[marking_j].mark()
562 for tmp_i
in range(i, len(structure)):
563 if self.
equal(structure[tmp_i], diff_structure[j]):
564 for marking_i
in range(i, tmp_i):
565 structure[marking_i].mark()
570 diff_structure[j].mark()
575 while i < len(structure):
579 while j < len(diff_structure):
580 diff_structure[j].mark()
586 Read differences file
588 full_component_name.property oldvalue=newvalue
590 AlgX.ToolA.SubToolB.SettingZ 45=46
591 It is possible to specify missing values, e.g:
592 AlgX.ToolA.SubToolB.SettingZ 45= means that now the old value should be ignored
593 AlgX.ToolA.SubToolB.SettingZ =46 means that now the new value should be ignored
594 AlgX.ToolA.SubToolB.SettingZ = means that any change of the value should be ignored
597 from collections
import defaultdict
598 differences = defaultdict(dict)
600 with open(fname,
"r")
as f:
602 if line[0] ==
"#" or line ==
"\n":
605 compAndProp, values = line.split(
" ")
606 comp, prop = compAndProp.rsplit(
".", 1)
607 o,n = values.split(
"=")
608 oldval,newval = o
if o
else None, n
if n
else None
610 differences[comp][prop] = (oldval,newval)
612 logger.info(
"... Read %d known differences from file: %s", count, fname)
613 logger.info(
"..... %s",
str(differences))