1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2016-2022 OPEN CASCADE
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 Manage SALOME configuration.
27 # Create configuration and set its parameters
28 tool.set("cfg", "name", "V7_6_0", "comment", "SALOME version 7.6.0")
30 tool.set("boost", "version", "1.52.2", "url", "https://sourceforge.net/projects/boost/files/boost/1.52.0/boost_1_52_0.tar.gz", "comment", "Set of libraries for the C++ programming language")
31 # Add patches to the product (note: patch file should be manually put to the patches directory)
32 tool.set("boost.patches.boost_patch_1.patch", "comment", "Fixes compilation problems on some platforms")
33 tool.set("boost.patches.boost_patch_2.patch", "comment", "gcc 5 compatibility")
34 # Inspect configuration: give all products
35 tool.get("cfg", "products")
36 # Inspect configuration: give parameters of the configuration
37 tool.get("cfg", "name")
38 tool.get("cfg", "comment")
39 # Inspect configuration: give parameters of the product
40 tool.get("boost", "version")
41 tool.get("boost", "url")
42 # Inspect configuration: give patches for the product
43 tool.get("boost", "patches")
44 # Verify configuration
45 conf_ok = tool.verify()
47 boost_ok = tool.verify("boost")
52 # Remove parameters of configuration
53 tool.remove("cfg", "comment")
54 # Remove parameters of product
55 tool.remove("boost", "url")
56 # Remove patch from product
57 tool.remove("boost.patches.boost_patch_2.patch")
65 import xml.etree.ElementTree as ET
67 exceptionClass = ET.ParseError
69 import xml.parsers.expat
70 exceptionClass = xml.parsers.expat.ExpatError
84 "supportedAttributes",
94 def defaultConfFile():
96 Return path to the default SALOME configuration file (string).
98 return os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "salome.xml"))
102 Return XML tag for configuration (string).
108 Return XML tag for software (string).
114 Return XML tag for patches set (string).
120 Return XML tag for patch (string).
126 Return XML attribute for name parameter (string).
132 Return XML attribute for comment parameter (string).
138 Return XML attribute for version parameter (string).
144 Return XML attribute for url parameter (string).
150 Return XML attribute for sha1 parameter (string).
156 Return list of all supported XML tags (list of strings).
158 return [configTag(), softwareTag(), patchesTag(), patchTag()]
160 def supportedAttributes():
162 Return list of all supported XML attributes (list of strings).
164 return [nameAttr(), commentAttr(), versionAttr(), urlAttr(), sha1Attr()]
166 def tagAttributes(tag, force = False):
168 Return list of attributes supported for the specified XML tag.
172 force: if True, all supported attributes are returned, including "special" ones.
174 Return value is list of strings.
177 if tag == configTag():
179 attrs[nameAttr()] = False # optional
180 attrs[commentAttr()] = False # optional
182 elif tag == softwareTag():
184 # note: name for software is specified implicitly via the target path
186 attrs[nameAttr()] = True # mandatory
188 attrs[versionAttr()] = True # mandatory
189 attrs[urlAttr()] = False # optional
190 attrs[commentAttr()] = False # optional
192 elif tag == patchTag():
194 # note: name for patch is specified implicitly via the target path
196 attrs[nameAttr()] = True # mandatory
199 attrs[urlAttr()] = False
200 attrs[commentAttr()] = False
201 attrs[sha1Attr()] = False
204 def tagChildren(tag):
206 Return supported child nodes' tags for given XML element.
207 Note: None means root 'config' XML element.
212 Return value is list of strings.
215 if tag == configTag(): ctags += [softwareTag()]
216 elif tag == softwareTag(): ctags += [patchesTag()]
217 elif tag == patchesTag(): ctags += [patchTag()]
218 elif tag is None: ctags += [configTag()]
223 Return parameter's alias for list of software are to be used with 'get' command (string).
225 return softwareTag()+"s"
229 Return parameter's alias for list patches to be used with 'get' command (string).
233 def childAlias(tag, param):
235 Return children node tag for children list alias.
239 param: children list alias.
241 Return child node tag name or None if alias is unsupported.
244 if tag == configTag():
245 if param == softwareAlias(): ctag = softwareTag()
247 elif tag == softwareTag():
248 if param == patchesAlias(): ctag = patchTag()
254 Return string used as a separator of path's component (string).
258 class CfgTool(object):
260 A tool to manage SALOME configuration files.
262 def __init__(self, cfgFile=None):
267 cfgFile: a path to the configuration file (string);
268 if not specified, default one is used.
271 self.cfgFile = cfgFile if cfgFile else defaultConfFile()
274 self.tree = ET.parse(self.cfgFile).getroot()
278 self.tree = self._new()
280 except exceptionClass as e:
281 if e.code == 3: # no element found, it's OK
282 self.tree = self._new()
284 raise Exception("bad XML file %s: %s" % (self.cfgFile, str(e)))
286 except Exception as e:
287 raise Exception("unknown error: %s" % str(e))
292 Return current encoding of the configuration file (string).
297 def setEncoding(self, value):
299 Set encoding for configuration file..
301 value: new encoding to be used when writing configuration file (string).
307 def get(self, target, param):
309 Get value of specified object's parameter.
310 Parameter can be a specific keyword that refers to the list of
311 child nodes. In this case the function returns list that
312 contains names of all child nodes.
315 target: object being checked (string).
316 param: parameter which value is being inspected.
318 Return value is string or list of strings.
320 path = self._processTarget(target)
322 elem = self._findPath(path)
324 raise Exception("no such target %s" % target)
326 if childAlias(tag, param):
327 result = self._children(elem, childAlias(tag, param))
329 elif param in tagAttributes(tag):
330 result = elem.get(param) if elem is not None and elem.get(param) else ""
333 raise Exception("unsupported parameter %s for target %s" % (param, target))
336 def set(self, target = None, *args, **kwargs):
338 Create or modify an object in the SALOME configuration.
341 target: object being created or modified (string); if not specified,
342 parameters of config itself will be modified.
343 args: positional arguments that describe parameters to be set (couple);
344 each couple of arguments specifies a parameter and its value
346 kwargs: keyword arguments - same as 'args' but specified in form of
349 path = self._processTarget(target)
352 # process keyword arguments
353 for param, value in list(kwargs.items()):
354 if param not in tagAttributes(tag):
355 raise Exception("unsupported parameter %s for target %s" % (param, target))
356 params[param] = value
358 # process positional arguments
362 if param not in tagAttributes(tag):
363 raise Exception("unsupported parameter %s for target %s" % (param, target))
365 if i+1 < len(args) and args[i+1] not in tagAttributes(tag):
369 params[param] = value
372 # create / modify target
373 elem = self._findPath(path, True)
374 for param, value in list(params.items()):
375 elem.set(param, value)
380 def remove(self, target, *args):
382 Remove object or its parameter(s).
385 target: object (string).
386 args: list of parameters which have to be removed (strings).
388 Return value is string.
390 path = self._processTarget(target)
392 elem = self._findPath(path)
394 raise Exception("no such target %s" % target)
396 # remove attributes of the target
397 # first check that all attributes are valid
399 if param not in tagAttributes(tag):
400 raise Exception("unsupported parameter %s for target %s" % (param, target))
401 elif param not in elem.attrib:
402 raise Exception("parameter %s is not set for target %s" % (param, target))
404 # now remove all attributes
406 elem.attrib.pop(param)
411 if elem == self.tree:
412 self.tree = self._new()
416 parent = self._findPath(path)
417 if parent is not None: parent.remove(elem)
423 def dump(self, target = None):
425 Dump the configuration.
428 target: object (string); if not specified, all configuration is dumped.
430 if target is not None:
431 path = self._processTarget(target)
432 elem = self._findPath(path)
434 raise Exception("no such target %s" % target)
442 def verify(self, target = None, errors = None):
447 target: object (string); if not specified, all configuration is verified.
449 Returns True if object is valid or False otherwise.
451 if errors is None: errors = []
452 if target is not None:
453 path = self._processTarget(target)
454 elem = self._findPath(path)
456 raise Exception("no such target %s" % target)
461 return self._verifyTag(elem, errors)
465 Clean the configuration.
467 self.tree = self._new()
471 def patchesDir(self):
473 Return path to the patches directory (string).
475 return os.path.join(os.path.dirname(self.cfgFile), "patches")
480 Create and return new empty root element.
482 Return values is an XML element (xml.etree.ElementTree.Element).
484 return ET.Element(configTag())
486 def _makeChild(self, elem, tag):
489 Create child element for given parent element.
492 elem: XML element (xml.etree.ElementTree.Element).
493 tag: tag of the child element
495 Return value is new XML element (xml.etree.ElementTree.Element).
497 child = ET.SubElement(elem, tag)
498 self.parents[child] = elem # set parent!!!
501 def _processTarget(self, target):
504 Check target and return XML path for it.
509 Return value is a list of tuples; each tuple is a couple
510 of path component and optional component's name.
512 if target is None: target = ""
513 comps = [i.strip() for i in target.split(pathSeparator())]
515 # add root to the path
516 path.append((configTag(), None))
517 if comps[0] in ["", "cfg", configTag()]: comps = comps[1:]
519 # second component of path can be only "software"
520 if not comps[0] or comps[0] in supportedTags() + supportedAttributes() + ["cfg"]:
521 raise Exception("badly specified target '%s'" % target)
522 path.append((softwareTag(), comps[0]))
526 # third component of path can be only "patches" or patch
527 if comps[0] not in [patchesTag(), patchTag()]:
528 raise Exception("badly specified target '%s'" % target)
529 path.append((patchesTag(), None))
533 # fourth component of path can be only a patch name
534 path.append((patchTag(), pathSeparator().join(comps)))
538 def _findPath(self, path, create=False):
541 Find and return XML element specified by its path.
542 If path does not exist and 'create' is True, XML element will be created.
545 path: XML element's path data (see _processTarget()).
546 create: flag that forces creating XML element if it does not exist
549 Return value is an XML element (xml.etree.ElementTree.Element).
552 if path[0][0] != configTag():
553 raise Exception("error parsing target path")
556 for tag, name in path[1:]:
558 children = [i for i in elem.getchildren() if i.tag == tag and i.get(nameAttr()) == name]
559 if len(children) > 1:
560 raise Exception("error parsing target path: more than one child element found")
561 elif len(children) == 1:
565 elem = self._makeChild(elem, tag)
566 elem.set(nameAttr(), name)
572 children = [i for i in elem.getchildren() if i.tag == tag]
573 if len(children) > 1:
574 raise Exception("error parsing target path: more than one child element found")
575 elif len(children) == 1:
579 elem = self._makeChild(elem, tag)
587 def _path(self, elem):
590 Construct path to the XML element.
593 elem: XML element (xml.etree.ElementTree.Element).
595 Return value is string.
599 attrs = tagAttributes(_obj.tag, True)
600 if nameAttr() in attrs and attrs[nameAttr()]:
601 if nameAttr() not in list(_obj.keys()): _name += " [unnamed]"
602 else: _name += " [%s]" % _obj.get(nameAttr())
606 while elem is not None:
607 path.append(_mkname(elem))
608 elem = self.parents.get(elem)
611 return pathSeparator().join(path)
613 def _children(self, elem, param):
616 Get names of children nodes for element.
619 elem: XML element (xml.etree.ElementTree.Element).
620 param: name of the children' tag.
622 Return value is a list of names of child elements (strings).
625 result += [i.get(nameAttr()) for i in \
626 [i for i in elem.getchildren() if i.tag == param and i.get(nameAttr())]]
627 for c in elem.getchildren():
628 result += self._children(c, param)
635 Write data tree content to the associated XML file.
638 with open(self.cfgFile, 'w') as f:
640 f.write('<?xml version="1.0" encoding="%s" ?>\n' % self.encoding() )
641 f.write('<!DOCTYPE config>\n')
643 self._prettify(self.tree)
645 et = ET.ElementTree(self.tree)
646 et.write(f, self.encoding())
650 raise Exception("can't write to %s: %s" % (self.cfgFile, e.strerror))
653 def _prettify(self, elem, level=0, hasSiblings=False):
656 Prettify XML file content.
659 elem: XML element (xml.etree.ElementTree.Element).
660 level: indentation level.
661 hasSiblings: True when item has siblings (i.e. this is not the last item
662 in the parent's children list).
665 children = elem.getchildren()
667 if hasSiblings: tail += indent * level
668 elif level > 0: tail += indent * (level-1)
670 if children: text = "\n" + indent * (level+1)
673 for i in range(len(children)):
674 self._prettify(children[i], level+1, len(children)>1 and i+1<len(children))
678 def _dump(self, elem, level=0):
684 elem: XML element (xml.etree.ElementTree.Element).
685 level: indentation level.
691 print("%s%s" % (indent * level, elem.tag))
692 attrs = tagAttributes(elem.tag, True)
693 format = "%" + "-%ds" % max([len(i) for i in supportedAttributes()]) + " : %s"
695 if a in list(elem.attrib.keys()):
696 print(indent*(level+1) + format % (a, elem.get(a)))
700 # dump all childrens recursively
701 for c in elem.getchildren():
702 self._dump(c, level+1)
706 def _checkConfig(self):
709 Verify configuration (used to check validity of associated XML file).
712 self._checkTag(self.tree, None, errors)
714 errors = ["Bad XML format:"] + ["- %s" % i for i in errors]
715 raise Exception("\n".join(errors))
718 def _checkTag(self, elem, tag, errors):
721 Check if format of given XML element is valid.
724 elem: XML element (xml.etree.ElementTree.Element).
725 tag: expected XML element's tag (string).
726 errors: output list to collect error messages (strings).
728 if elem.tag not in tagChildren(tag):
729 errors.append("bad XML element: %s" % elem.tag)
732 attrs = list(elem.keys())
734 if attr not in tagAttributes(elem.tag, True):
735 errors.append("unsupported attribute '%s' for XML element '%s'" % (attr, elem.tag))
738 # check all childrens recursively
739 children = elem.getchildren()
740 for child in children:
741 self.parents[child] = elem # set parent!!!
742 self._checkTag(child, elem.tag, errors)
747 def _verifyTag(self, elem, errors):
750 Verify given XML element is valid in terms of SALOME configuration.
753 elem: XML element (xml.etree.ElementTree.Element).
754 errors: output list to collect error messages (strings).
756 attrs = tagAttributes(elem.tag, True)
757 # check mandatory attributes
759 if attrs[attr] and (attr not in list(elem.keys()) or not elem.get(attr).strip()):
760 errors.append("mandatory parameter '%s' of object '%s' is not set" % (attr, self._path(elem)))
763 # specific check for particular XML element
765 self._checkObject(elem)
766 except Exception as e:
767 errors.append("%s : %s" % (self._path(elem), str(e)))
768 # check all childrens recursively
769 for c in elem.getchildren():
770 self._verifyTag(c, errors)
772 return len(errors) == 0
774 def _checkObject(self, elem):
777 Perform specific check for given XML element.
779 Raises an exception that if object is invalid.
782 elem: XML element (xml.etree.ElementTree.Element).
784 if elem.tag == patchTag():
785 filename = elem.get(nameAttr())
786 url = elem.get(urlAttr())
787 if filename and not url:
788 # if url is not given, we should check that file is present locally
789 filepath = os.path.join(self.patchesDir(), filename)
790 if not os.path.exists(filepath):
791 raise Exception("patch file %s is not found" % filepath)
794 # TODO: we might check validity of URL here (see urlparse)!