pungi/pungi/wrappers/variants.py
Lubomír Sedlář c5f878330c Remove shebangs from non-executable files
Some modules can be executed as a sort-of test. However, the files do
not have executable bit set, so there is no need for them to have
shebangs. If someone wants to call them directly, they should do so via
python.

Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
2016-09-23 10:26:43 +02:00

349 lines
13 KiB
Python
Executable File

# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://gnu.org/licenses/>.
from __future__ import print_function
import os
import sys
import copy
import lxml.etree
# HACK: define cmp in python3
if sys.version_info[0] == 3:
def cmp(a, b):
return (a > b) - (a < b)
def get_variants_dtd(logger=None):
"""
Find the DTD for variants file. First look into the system directory, and
fall back to local directory.
"""
variants_dtd = "/usr/share/pungi/variants.dtd"
if not os.path.isfile(variants_dtd):
devel_variants_dtd = os.path.normpath(os.path.realpath(
os.path.join(os.path.dirname(__file__), "..", "..", "share", "variants.dtd")))
msg = "Variants DTD not found: %s" % variants_dtd
if os.path.isfile(devel_variants_dtd):
if logger:
logger.warning("%s", msg)
logger.warning("Using alternative DTD: %s", devel_variants_dtd)
variants_dtd = devel_variants_dtd
else:
raise RuntimeError(msg)
return variants_dtd
class VariantsXmlParser(object):
def __init__(self, file_obj, tree_arches=None, tree_variants=None, logger=None):
self.tree = lxml.etree.parse(file_obj)
with open(get_variants_dtd(logger), 'r') as f:
self.dtd = lxml.etree.DTD(f)
self.addons = {}
self.layered_products = {}
self.tree_arches = tree_arches
self.tree_variants = tree_variants
self.logger = logger
self.validate()
def _is_true(self, value):
if value == "true":
return True
if value == "false":
return False
raise ValueError("Invalid boolean value in variants XML: %s" % value)
def validate(self):
if not self.dtd.validate(self.tree):
errors = [str(i) for i in self.dtd.error_log.filter_from_errors()]
raise ValueError("Variants XML doesn't validate:\n%s" % "\n".join(errors))
def parse_variant_node(self, variant_node, parent=None):
variant_dict = {
"id": str(variant_node.attrib["id"]),
"name": str(variant_node.attrib["name"]),
"type": str(variant_node.attrib["type"]),
"arches": [str(i) for i in variant_node.xpath("arches/arch/text()")],
"groups": [],
"environments": [],
"buildinstallpackages": [],
"is_empty": bool(variant_node.attrib.get("is_empty", False)),
"parent": parent,
}
if self.tree_arches:
variant_dict["arches"] = [i for i in variant_dict["arches"] if i in self.tree_arches]
if not variant_dict["arches"]:
if self.logger:
self.logger.info('Excluding variant %s: all its arches are filtered.' % variant_dict['id'])
return None
for grouplist_node in variant_node.xpath("groups"):
for group_node in grouplist_node.xpath("group"):
group = {
"name": str(group_node.text),
"glob": self._is_true(group_node.attrib.get("glob", "false")),
"default": None,
"uservisible": None,
}
default = group_node.attrib.get("default")
if default is not None:
group["default"] = self._is_true(default)
uservisible = group_node.attrib.get("uservisible")
if uservisible is not None:
group["uservisible"] = self._is_true(uservisible)
variant_dict["groups"].append(group)
for environments_node in variant_node.xpath("environments"):
for environment_node in environments_node.xpath("environment"):
environment = {
"name": str(environment_node.text),
"display_order": None,
}
display_order = environment_node.attrib.get("display_order")
if display_order is not None:
environment["display_order"] = int(display_order)
variant_dict["environments"].append(environment)
for buildinstallpackages_node in variant_node.xpath("buildinstallpackages"):
for package_node in buildinstallpackages_node.xpath("package"):
variant_dict["buildinstallpackages"].append(package_node.text)
variant = Variant(**variant_dict)
if variant.type == "layered-product":
release_node = variant_node.xpath("release")[0]
variant.release_name = str(release_node.attrib["name"])
variant.release_version = str(release_node.attrib["version"])
variant.release_short = str(release_node.attrib["short"])
contains_optional = False
for child_node in variant_node.xpath("variants/variant"):
child_variant = self.parse_variant_node(child_node, variant)
if not self.add_child(child_variant, variant):
continue
if child_variant.type == "optional":
contains_optional = True
has_optional = self._is_true(variant_node.attrib.get("has_optional", "false"))
if has_optional and not contains_optional:
optional = Variant(id="optional", name="optional", type="optional",
arches=variant.arches, groups=[], parent=variant)
self.add_child(optional, variant)
for ref in variant_node.xpath("variants/ref/@id"):
try:
child_variant = self.parse_variant_node(self.addons[ref], variant)
except KeyError:
raise RuntimeError("Variant %s references non-existing variant %s"
% (variant.uid, ref))
self.add_child(child_variant, variant)
# XXX: top-level optional
# for ref in variant_node.xpath("variants/ref/@id"):
# variant["variants"].append(copy.deepcopy(addons[ref]))
return variant
def _is_excluded(self, variant):
if self.tree_variants and variant.uid not in self.tree_variants:
if self.logger:
self.logger.info('Excluding variant %s: filtered by configuration.' % variant)
return True
return False
def add_child(self, child, parent):
if not child or self._is_excluded(child):
return None
parent.add_variant(child)
return child
def parse(self):
# we allow top-level addon definitions which can be referenced in variants
for variant_node in self.tree.xpath("/variants/variant[@type='addon']"):
variant_id = str(variant_node.attrib["id"])
self.addons[variant_id] = variant_node
for variant_node in self.tree.xpath("/variants/variant[@type='layered-product']"):
variant_id = str(variant_node.attrib["id"])
self.addons[variant_id] = variant_node
result = {}
for variant_node in self.tree.xpath("/variants/variant[@type='variant']"):
variant = self.parse_variant_node(variant_node)
if not variant or self._is_excluded(variant):
continue
result[variant.id] = variant
for variant_node in self.tree.xpath("/variants/variant[not(@type='variant' or @type='addon' or @type='layered-product')]"):
raise RuntimeError("Invalid variant type at the top-level: %s" % variant_node.attrib["type"])
return result
class Variant(object):
def __init__(self, id, name, type, arches, groups, environments=None,
buildinstallpackages=None, is_empty=False, parent=None):
environments = environments or []
buildinstallpackages = buildinstallpackages or []
self.id = id
self.name = name
self.type = type
self.arches = sorted(copy.deepcopy(arches))
self.groups = sorted(copy.deepcopy(groups), lambda x, y: cmp(x["name"], y["name"]))
self.environments = sorted(copy.deepcopy(environments), lambda x, y: cmp(x["name"], y["name"]))
self.buildinstallpackages = sorted(buildinstallpackages)
self.variants = {}
self.parent = parent
self.is_empty = is_empty
def __getitem__(self, name):
return self.variants[name]
def __str__(self):
return self.uid
def __repr__(self):
return 'Variant(id="{0.id}", name="{0.name}", type="{0.type}", parent={0.parent})'.format(self)
def __cmp__(self, other):
# variant < addon, layered-product < optional
if self.type == other.type:
return cmp(self.uid, other.uid)
if self.type == "variant":
return -1
if other.type == "variant":
return 1
if self.type == "optional":
return 1
if other.type == "optional":
return -1
return cmp(self.uid, other.uid)
@property
def uid(self):
if self.parent:
return "%s-%s" % (self.parent, self.id)
return self.id
def add_variant(self, variant):
"""Add a variant object to the child variant list."""
if variant.id in self.variants:
return
if self.type != "variant":
raise RuntimeError("Only 'variant' can contain another variants.")
if variant.id == self.id:
# due to os/<variant.id> path -- addon id would conflict with parent variant id
raise RuntimeError("Child variant id must be different than parent variant id: %s" % variant.id)
# sometimes an addon or layered product can be part of multiple variants with different set of arches
arches = sorted(set(self.arches).intersection(set(variant.arches)))
if self.arches and not arches:
raise RuntimeError("%s: arch list %s does not intersect with parent arch list: %s" % (variant, variant.arches, self.arches))
variant.arches = arches
self.variants[variant.id] = variant
variant.parent = self
def get_groups(self, arch=None, types=None, recursive=False):
"""Return list of groups, default types is ["self"]"""
types = types or ["self"]
result = copy.deepcopy(self.groups)
for variant in self.get_variants(arch=arch, types=types, recursive=recursive):
if variant == self:
# XXX
continue
for group in variant.get_groups(arch=arch, types=types, recursive=recursive):
if group not in result:
result.append(group)
return result
def get_variants(self, arch=None, types=None, recursive=False):
"""
Return all variants of given arch and types.
Supported variant types:
self - include the top-level ("self") variant as well
addon
variant
optional
"""
types = types or []
result = []
if arch and arch not in self.arches + ["src"]:
return result
if "self" in types:
result.append(self)
for variant in self.variants.values():
if types and variant.type not in types:
continue
if arch and arch not in variant.arches + ["src"]:
continue
result.append(variant)
if recursive:
result.extend(variant.get_variants(types=[i for i in types if i != "self"], recursive=True))
return result
def get_addons(self, arch=None):
"""Return all 'addon' child variants. No recursion."""
return self.get_variants(arch=arch, types=["addon"], recursive=False)
def get_layered_products(self, arch=None):
"""Return all 'layered-product' child variants. No recursion."""
return self.get_variants(arch=arch, types=["layered-product"], recursive=False)
def get_optional(self, arch=None):
"""Return all 'optional' child variants. No recursion."""
return self.get_variants(arch=arch, types=["optional"], recursive=False)
def main(argv):
import optparse
parser = optparse.OptionParser("%prog <variants.xml>")
opts, args = parser.parse_args(argv)
if len(args) != 1:
parser.error("Please provide a <variants.xml> file.")
file_path = args[0]
try:
file_obj = open(file_path, "r")
except Exception as ex:
print(str(ex), file=sys.stderr)
sys.exit(1)
for top_level_variant in list(VariantsXmlParser(file_obj).parse().values()):
for i in top_level_variant.get_variants(types=["self", "variant", "addon", "layered-product", "optional"], recursive=True):
print("ID: %-30s NAME: %-40s TYPE: %-12s UID: %s" % (i.id, i.name, i.type, i))
print(" ARCHES: %s" % ", ".join(sorted(i.arches)))
for group in i.groups:
print(" GROUP: %(name)-40s GLOB: %(glob)-5s DEFAULT: %(default)-5s USERVISIBLE: %(uservisible)-5s" % group)
for env in i.environments:
print(" ENV: %(name)-40s DISPLAY_ORDER: %(display_order)s" % env)
print()
if __name__ == "__main__":
main(sys.argv[1:])