Blame dnf/comps.py

Packit Service 21c75c
# comps.py
Packit Service 21c75c
# Interface to libcomps.
Packit Service 21c75c
#
Packit Service 21c75c
# Copyright (C) 2013-2018 Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
# This copyrighted material is made available to anyone wishing to use,
Packit Service 21c75c
# modify, copy, or redistribute it subject to the terms and conditions of
Packit Service 21c75c
# the GNU General Public License v.2, or (at your option) any later version.
Packit Service 21c75c
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service 21c75c
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit Service 21c75c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit Service 21c75c
# Public License for more details.  You should have received a copy of the
Packit Service 21c75c
# GNU General Public License along with this program; if not, write to the
Packit Service 21c75c
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 21c75c
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit Service 21c75c
# source code or documentation are not subject to the GNU General Public
Packit Service 21c75c
# License and may only be used or replicated with the express permission of
Packit Service 21c75c
# Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
Packit Service 21c75c
from __future__ import absolute_import
Packit Service 21c75c
from __future__ import print_function
Packit Service 21c75c
from __future__ import unicode_literals
Packit Service 21c75c
Packit Service 21c75c
import libdnf.transaction
Packit Service 21c75c
Packit Service 21c75c
from dnf.exceptions import CompsError
Packit Service 21c75c
from dnf.i18n import _, ucd
Packit Service 21c75c
from functools import reduce
Packit Service 21c75c
Packit Service 21c75c
import dnf.i18n
Packit Service 21c75c
import dnf.util
Packit Service 21c75c
import fnmatch
Packit Service 21c75c
import gettext
Packit Service 21c75c
import itertools
Packit Service 21c75c
import libcomps
Packit Service 21c75c
import locale
Packit Service 21c75c
import logging
Packit Service 21c75c
import operator
Packit Service 21c75c
import re
Packit Service 21c75c
import sys
Packit Service 21c75c
Packit Service 21c75c
logger = logging.getLogger("dnf")
Packit Service 21c75c
Packit Service 21c75c
# :api :binformat
Packit Service 21c75c
CONDITIONAL = libdnf.transaction.CompsPackageType_CONDITIONAL
Packit Service 21c75c
DEFAULT     = libdnf.transaction.CompsPackageType_DEFAULT
Packit Service 21c75c
MANDATORY   = libdnf.transaction.CompsPackageType_MANDATORY
Packit Service 21c75c
OPTIONAL    = libdnf.transaction.CompsPackageType_OPTIONAL
Packit Service 21c75c
Packit Service 21c75c
ALL_TYPES = CONDITIONAL | DEFAULT | MANDATORY | OPTIONAL
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _internal_comps_length(comps):
Packit Service 21c75c
    collections = (comps.categories, comps.groups, comps.environments)
Packit Service 21c75c
    return reduce(operator.__add__, map(len, collections))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _first_if_iterable(seq):
Packit Service 21c75c
    if seq is None:
Packit Service 21c75c
        return None
Packit Service 21c75c
    return dnf.util.first(seq)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _by_pattern(pattern, case_sensitive, sqn):
Packit Service 21c75c
    """Return items from sqn matching either exactly or glob-wise."""
Packit Service 21c75c
Packit Service 21c75c
    pattern = dnf.i18n.ucd(pattern)
Packit Service 21c75c
    exact = {g for g in sqn if g.name == pattern or g.id == pattern}
Packit Service 21c75c
    if exact:
Packit Service 21c75c
        return exact
Packit Service 21c75c
Packit Service 21c75c
    if case_sensitive:
Packit Service 21c75c
        match = re.compile(fnmatch.translate(pattern)).match
Packit Service 21c75c
    else:
Packit Service 21c75c
        match = re.compile(fnmatch.translate(pattern), flags=re.I).match
Packit Service 21c75c
Packit Service 21c75c
    ret = set()
Packit Service 21c75c
    for g in sqn:
Packit Service 21c75c
        if match(g.id):
Packit Service 21c75c
            ret.add(g)
Packit Service 21c75c
        elif g.name is not None and match(g.name):
Packit Service 21c75c
            ret.add(g)
Packit Service 21c75c
        elif g.ui_name is not None and match(g.ui_name):
Packit Service 21c75c
            ret.add(g)
Packit Service 21c75c
Packit Service 21c75c
    return ret
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _fn_display_order(group):
Packit Service 21c75c
    return sys.maxsize if group.display_order is None else group.display_order
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def install_or_skip(install_fnc, grp_or_env_id, types, exclude=None,
Packit Service 21c75c
                    strict=True, exclude_groups=None):
Packit Service 21c75c
    """Either mark in persistor as installed given `grp_or_env` (group
Packit Service 21c75c
       or environment) or skip it (if it's already installed).
Packit Service 21c75c
       `install_fnc` has to be Solver._group_install
Packit Service 21c75c
       or Solver._environment_install.
Packit Service 21c75c
       """
Packit Service 21c75c
    try:
Packit Service 21c75c
        return install_fnc(grp_or_env_id, types, exclude, strict, exclude_groups)
Packit Service 21c75c
    except dnf.comps.CompsError as e:
Packit Service 21c75c
        logger.warning("%s, %s", ucd(e)[:-1], _("skipping."))
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class _Langs(object):
Packit Service 21c75c
Packit Service 21c75c
    """Get all usable abbreviations for the current language."""
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        self.last_locale = None
Packit Service 21c75c
        self.cache = None
Packit Service 21c75c
Packit Service 21c75c
    @staticmethod
Packit Service 21c75c
    def _dotted_locale_str():
Packit Service 21c75c
        lcl = locale.getlocale(locale.LC_MESSAGES)
Packit Service 21c75c
        if lcl == (None, None):
Packit Service 21c75c
            return 'C'
Packit Service 21c75c
        return '.'.join(lcl)
Packit Service 21c75c
Packit Service 21c75c
    def get(self):
Packit Service 21c75c
        current_locale = self._dotted_locale_str()
Packit Service 21c75c
        if self.last_locale == current_locale:
Packit Service 21c75c
            return self.cache
Packit Service 21c75c
Packit Service 21c75c
        self.cache = []
Packit Service 21c75c
        locales = [current_locale]
Packit Service 21c75c
        if current_locale != 'C':
Packit Service 21c75c
            locales.append('C')
Packit Service 21c75c
        for l in locales:
Packit Service 21c75c
            for nlang in gettext._expand_lang(l):
Packit Service 21c75c
                if nlang not in self.cache:
Packit Service 21c75c
                    self.cache.append(nlang)
Packit Service 21c75c
Packit Service 21c75c
        self.last_locale = current_locale
Packit Service 21c75c
        return self.cache
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class CompsQuery(object):
Packit Service 21c75c
Packit Service 21c75c
    AVAILABLE = 1
Packit Service 21c75c
    INSTALLED = 2
Packit Service 21c75c
Packit Service 21c75c
    ENVIRONMENTS = 1
Packit Service 21c75c
    GROUPS = 2
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self, comps, history, kinds, status):
Packit Service 21c75c
        self.comps = comps
Packit Service 21c75c
        self.history = history
Packit Service 21c75c
        self.kinds = kinds
Packit Service 21c75c
        self.status = status
Packit Service 21c75c
Packit Service 21c75c
    def _get_groups(self, available, installed):
Packit Service 21c75c
        result = set()
Packit Service 21c75c
        if self.status & self.AVAILABLE:
Packit Service 21c75c
            result.update({i.id for i in available})
Packit Service 21c75c
        if self.status & self.INSTALLED:
Packit Service 21c75c
            for i in installed:
Packit Service 21c75c
                group = i.getCompsGroupItem()
Packit Service 21c75c
                if not group:
Packit Service 21c75c
                    continue
Packit Service 21c75c
                result.add(group.getGroupId())
Packit Service 21c75c
        return result
Packit Service 21c75c
Packit Service 21c75c
    def _get_envs(self, available, installed):
Packit Service 21c75c
        result = set()
Packit Service 21c75c
        if self.status & self.AVAILABLE:
Packit Service 21c75c
            result.update({i.id for i in available})
Packit Service 21c75c
        if self.status & self.INSTALLED:
Packit Service 21c75c
            for i in installed:
Packit Service 21c75c
                env = i.getCompsEnvironmentItem()
Packit Service 21c75c
                if not env:
Packit Service 21c75c
                    continue
Packit Service 21c75c
                result.add(env.getEnvironmentId())
Packit Service 21c75c
        return result
Packit Service 21c75c
Packit Service 21c75c
    def get(self, *patterns):
Packit Service 21c75c
        res = dnf.util.Bunch()
Packit Service 21c75c
        res.environments = []
Packit Service 21c75c
        res.groups = []
Packit Service 21c75c
        for pat in patterns:
Packit Service 21c75c
            envs = grps = []
Packit Service 21c75c
            if self.kinds & self.ENVIRONMENTS:
Packit Service 21c75c
                available = self.comps.environments_by_pattern(pat)
Packit Service 21c75c
                installed = self.history.env.search_by_pattern(pat)
Packit Service 21c75c
                envs = self._get_envs(available, installed)
Packit Service 21c75c
                res.environments.extend(envs)
Packit Service 21c75c
            if self.kinds & self.GROUPS:
Packit Service 21c75c
                available = self.comps.groups_by_pattern(pat)
Packit Service 21c75c
                installed = self.history.group.search_by_pattern(pat)
Packit Service 21c75c
                grps = self._get_groups(available, installed)
Packit Service 21c75c
                res.groups.extend(grps)
Packit Service 21c75c
            if not envs and not grps:
Packit Service 21c75c
                if self.status == self.INSTALLED:
Packit Service 21c75c
                    msg = _("Module or Group '%s' is not installed.") % ucd(pat)
Packit Service 21c75c
                elif self.status == self.AVAILABLE:
Packit Service 21c75c
                    msg = _("Module or Group '%s' is not available.") % ucd(pat)
Packit Service 21c75c
                else:
Packit Service 21c75c
                    msg = _("Module or Group '%s' does not exist.") % ucd(pat)
Packit Service 21c75c
                raise CompsError(msg)
Packit Service 21c75c
        return res
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class Forwarder(object):
Packit Service 21c75c
    def __init__(self, iobj, langs):
Packit Service 21c75c
        self._i = iobj
Packit Service 21c75c
        self._langs = langs
Packit Service 21c75c
Packit Service 21c75c
    def __getattr__(self, name):
Packit Service 21c75c
        return getattr(self._i, name)
Packit Service 21c75c
Packit Service 21c75c
    def _ui_text(self, default, dct):
Packit Service 21c75c
        for l in self._langs.get():
Packit Service 21c75c
            t = dct.get(l)
Packit Service 21c75c
            if t is not None:
Packit Service 21c75c
                return t
Packit Service 21c75c
        return default
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def ui_description(self):
Packit Service 21c75c
        return self._ui_text(self.desc, self.desc_by_lang)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def ui_name(self):
Packit Service 21c75c
        return self._ui_text(self.name, self.name_by_lang)
Packit Service 21c75c
Packit Service 21c75c
class Category(Forwarder):
Packit Service 21c75c
    # :api
Packit Service 21c75c
    def __init__(self, iobj, langs, group_factory):
Packit Service 21c75c
        super(Category, self).__init__(iobj, langs)
Packit Service 21c75c
        self._group_factory = group_factory
Packit Service 21c75c
Packit Service 21c75c
    def _build_group(self, grp_id):
Packit Service 21c75c
        grp = self._group_factory(grp_id.name)
Packit Service 21c75c
        if grp is None:
Packit Service 21c75c
            msg = "no group '%s' from category '%s'"
Packit Service 21c75c
            raise ValueError(msg % (grp_id.name, self.id))
Packit Service 21c75c
        return grp
Packit Service 21c75c
Packit Service 21c75c
    def groups_iter(self):
Packit Service 21c75c
        for grp_id in self.group_ids:
Packit Service 21c75c
            yield self._build_group(grp_id)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def groups(self):
Packit Service 21c75c
        return list(self.groups_iter())
Packit Service 21c75c
Packit Service 21c75c
class Environment(Forwarder):
Packit Service 21c75c
    # :api
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self, iobj, langs, group_factory):
Packit Service 21c75c
        super(Environment, self).__init__(iobj, langs)
Packit Service 21c75c
        self._group_factory = group_factory
Packit Service 21c75c
Packit Service 21c75c
    def _build_group(self, grp_id):
Packit Service 21c75c
        grp = self._group_factory(grp_id.name)
Packit Service 21c75c
        if grp is None:
Packit Service 21c75c
            msg = "no group '%s' from environment '%s'"
Packit Service 21c75c
            raise ValueError(msg % (grp_id.name, self.id))
Packit Service 21c75c
        return grp
Packit Service 21c75c
Packit Service 21c75c
    def _build_groups(self, ids):
Packit Service 21c75c
        groups = []
Packit Service 21c75c
        for gi in ids:
Packit Service 21c75c
            try:
Packit Service 21c75c
                groups.append(self._build_group(gi))
Packit Service 21c75c
            except ValueError as e:
Packit Service 21c75c
                logger.error(e)
Packit Service 21c75c
Packit Service 21c75c
        return groups
Packit Service 21c75c
Packit Service 21c75c
    def groups_iter(self):
Packit Service 21c75c
        for grp_id in itertools.chain(self.group_ids, self.option_ids):
Packit Service 21c75c
            try:
Packit Service 21c75c
                yield self._build_group(grp_id)
Packit Service 21c75c
            except ValueError as e:
Packit Service 21c75c
                logger.error(e)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def mandatory_groups(self):
Packit Service 21c75c
        return self._build_groups(self.group_ids)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def optional_groups(self):
Packit Service 21c75c
        return self._build_groups(self.option_ids)
Packit Service 21c75c
Packit Service 21c75c
class Group(Forwarder):
Packit Service 21c75c
    # :api
Packit Service 21c75c
    def __init__(self, iobj, langs, pkg_factory):
Packit Service 21c75c
        super(Group, self).__init__(iobj, langs)
Packit Service 21c75c
        self._pkg_factory = pkg_factory
Packit Service 21c75c
        self.selected = iobj.default
Packit Service 21c75c
Packit Service 21c75c
    def _packages_of_type(self, type_):
Packit Service 21c75c
        return [pkg for pkg in self.packages if pkg.type == type_]
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def conditional_packages(self):
Packit Service 21c75c
        return self._packages_of_type(libcomps.PACKAGE_TYPE_CONDITIONAL)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def default_packages(self):
Packit Service 21c75c
        return self._packages_of_type(libcomps.PACKAGE_TYPE_DEFAULT)
Packit Service 21c75c
Packit Service 21c75c
    def packages_iter(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return map(self._pkg_factory, self.packages)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def mandatory_packages(self):
Packit Service 21c75c
        return self._packages_of_type(libcomps.PACKAGE_TYPE_MANDATORY)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def optional_packages(self):
Packit Service 21c75c
        return self._packages_of_type(libcomps.PACKAGE_TYPE_OPTIONAL)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def visible(self):
Packit Service 21c75c
        return self._i.uservisible
Packit Service 21c75c
Packit Service 21c75c
class Package(Forwarder):
Packit Service 21c75c
    """Represents comps package data. :api"""
Packit Service 21c75c
Packit Service 21c75c
    _OPT_MAP = {
Packit Service 21c75c
        libcomps.PACKAGE_TYPE_CONDITIONAL : CONDITIONAL,
Packit Service 21c75c
        libcomps.PACKAGE_TYPE_DEFAULT     : DEFAULT,
Packit Service 21c75c
        libcomps.PACKAGE_TYPE_MANDATORY   : MANDATORY,
Packit Service 21c75c
        libcomps.PACKAGE_TYPE_OPTIONAL    : OPTIONAL,
Packit Service 21c75c
    }
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self, ipkg):
Packit Service 21c75c
        self._i = ipkg
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def name(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return self._i.name
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def option_type(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return self._OPT_MAP[self.type]
Packit Service 21c75c
Packit Service 21c75c
class Comps(object):
Packit Service 21c75c
    # :api
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        self._i = libcomps.Comps()
Packit Service 21c75c
        self._langs = _Langs()
Packit Service 21c75c
Packit Service 21c75c
    def __len__(self):
Packit Service 21c75c
        return _internal_comps_length(self._i)
Packit Service 21c75c
Packit Service 21c75c
    def _build_category(self, icategory):
Packit Service 21c75c
        return Category(icategory, self._langs, self._group_by_id)
Packit Service 21c75c
Packit Service 21c75c
    def _build_environment(self, ienvironment):
Packit Service 21c75c
        return Environment(ienvironment, self._langs, self._group_by_id)
Packit Service 21c75c
Packit Service 21c75c
    def _build_group(self, igroup):
Packit Service 21c75c
        return Group(igroup, self._langs, self._build_package)
Packit Service 21c75c
Packit Service 21c75c
    def _build_package(self, ipkg):
Packit Service 21c75c
        return Package(ipkg)
Packit Service 21c75c
Packit Service 21c75c
    def _add_from_xml_filename(self, fn):
Packit Service 21c75c
        comps = libcomps.Comps()
Packit Service 21c75c
        try:
Packit Service 21c75c
            comps.fromxml_f(fn)
Packit Service 21c75c
        except libcomps.ParserError:
Packit Service 21c75c
            errors = comps.get_last_errors()
Packit Service 21c75c
            raise CompsError(' '.join(errors))
Packit Service 21c75c
        self._i += comps
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def categories(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return list(self.categories_iter())
Packit Service 21c75c
Packit Service 21c75c
    def category_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        cats = self.categories_by_pattern(pattern, case_sensitive)
Packit Service 21c75c
        return _first_if_iterable(cats)
Packit Service 21c75c
Packit Service 21c75c
    def categories_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        return _by_pattern(pattern, case_sensitive, self.categories)
Packit Service 21c75c
Packit Service 21c75c
    def categories_iter(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return (self._build_category(c) for c in self._i.categories)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def environments(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return sorted(self.environments_iter(), key=_fn_display_order)
Packit Service 21c75c
Packit Service 21c75c
    def _environment_by_id(self, id):
Packit Service 21c75c
        assert dnf.util.is_string_type(id)
Packit Service 21c75c
        return dnf.util.first(g for g in self.environments_iter() if g.id == id)
Packit Service 21c75c
Packit Service 21c75c
    def environment_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        envs = self.environments_by_pattern(pattern, case_sensitive)
Packit Service 21c75c
        return _first_if_iterable(envs)
Packit Service 21c75c
Packit Service 21c75c
    def environments_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        envs = list(self.environments_iter())
Packit Service 21c75c
        found_envs = _by_pattern(pattern, case_sensitive, envs)
Packit Service 21c75c
        return sorted(found_envs, key=_fn_display_order)
Packit Service 21c75c
Packit Service 21c75c
    def environments_iter(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return (self._build_environment(e) for e in self._i.environments)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def groups(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return sorted(self.groups_iter(), key=_fn_display_order)
Packit Service 21c75c
Packit Service 21c75c
    def _group_by_id(self, id_):
Packit Service 21c75c
        assert dnf.util.is_string_type(id_)
Packit Service 21c75c
        return dnf.util.first(g for g in self.groups_iter() if g.id == id_)
Packit Service 21c75c
Packit Service 21c75c
    def group_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        grps = self.groups_by_pattern(pattern, case_sensitive)
Packit Service 21c75c
        return _first_if_iterable(grps)
Packit Service 21c75c
Packit Service 21c75c
    def groups_by_pattern(self, pattern, case_sensitive=False):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        assert dnf.util.is_string_type(pattern)
Packit Service 21c75c
        grps = _by_pattern(pattern, case_sensitive, list(self.groups_iter()))
Packit Service 21c75c
        return sorted(grps, key=_fn_display_order)
Packit Service 21c75c
Packit Service 21c75c
    def groups_iter(self):
Packit Service 21c75c
        # :api
Packit Service 21c75c
        return (self._build_group(g) for g in self._i.groups)
Packit Service 21c75c
Packit Service 21c75c
class CompsTransPkg(object):
Packit Service 21c75c
    def __init__(self, pkg_or_name):
Packit Service 21c75c
        if dnf.util.is_string_type(pkg_or_name):
Packit Service 21c75c
            # from package name
Packit Service 21c75c
            self.basearchonly = False
Packit Service 21c75c
            self.name = pkg_or_name
Packit Service 21c75c
            self.optional = True
Packit Service 21c75c
            self.requires = None
Packit Service 21c75c
        elif isinstance(pkg_or_name, libdnf.transaction.CompsGroupPackage):
Packit Service 21c75c
            # from swdb package
Packit Service 21c75c
            # TODO:
Packit Service 21c75c
            self.basearchonly = False
Packit Service 21c75c
            # self.basearchonly = pkg_or_name.basearchonly
Packit Service 21c75c
            self.name = pkg_or_name.getName()
Packit Service 21c75c
            self.optional = pkg_or_name.getPackageType() & libcomps.PACKAGE_TYPE_OPTIONAL
Packit Service 21c75c
            # TODO:
Packit Service 21c75c
            self.requires = None
Packit Service 21c75c
            # self.requires = pkg_or_name.requires
Packit Service 21c75c
        else:
Packit Service 21c75c
            # from comps package
Packit Service 21c75c
            self.basearchonly = pkg_or_name.basearchonly
Packit Service 21c75c
            self.name = pkg_or_name.name
Packit Service 21c75c
            self.optional = pkg_or_name.type & libcomps.PACKAGE_TYPE_OPTIONAL
Packit Service 21c75c
            self.requires = pkg_or_name.requires
Packit Service 21c75c
Packit Service 21c75c
    def __eq__(self, other):
Packit Service 21c75c
        return (self.name == other.name and
Packit Service 21c75c
                self.basearchonly == self.basearchonly and
Packit Service 21c75c
                self.optional == self.optional and
Packit Service 21c75c
                self.requires == self.requires)
Packit Service 21c75c
Packit Service 21c75c
    def __str__(self):
Packit Service 21c75c
        return self.name
Packit Service 21c75c
Packit Service 21c75c
    def __hash__(self):
Packit Service 21c75c
        return hash((self.name,
Packit Service 21c75c
                    self.basearchonly,
Packit Service 21c75c
                    self.optional,
Packit Service 21c75c
                    self.requires))
Packit Service 21c75c
Packit Service 21c75c
class TransactionBunch(object):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        self._install = set()
Packit Service 21c75c
        self._install_opt = set()
Packit Service 21c75c
        self._remove = set()
Packit Service 21c75c
        self._upgrade = set()
Packit Service 21c75c
Packit Service 21c75c
    def __iadd__(self, other):
Packit Service 21c75c
        self._install.update(other._install)
Packit Service 21c75c
        self._install_opt.update(other._install_opt)
Packit Service 21c75c
        self._upgrade.update(other._upgrade)
Packit Service 21c75c
        self._remove = (self._remove | other._remove) - \
Packit Service 21c75c
            self._install - self._install_opt - self._upgrade
Packit Service 21c75c
        return self
Packit Service 21c75c
Packit Service 21c75c
    def __len__(self):
Packit Service 21c75c
        return len(self.install) + len(self.install_opt) + len(self.upgrade) + len(self.remove)
Packit Service 21c75c
Packit Service 21c75c
    @staticmethod
Packit Service 21c75c
    def _set_value(param, val):
Packit Service 21c75c
        for item in val:
Packit Service 21c75c
            if isinstance(item, CompsTransPkg):
Packit Service 21c75c
                param.add(item)
Packit Service 21c75c
            else:
Packit Service 21c75c
                param.add(CompsTransPkg(item))
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def install(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        Packages to be installed with strict=True - transaction will
Packit Service 21c75c
        fail if they cannot be installed due to dependency errors etc.
Packit Service 21c75c
        """
Packit Service 21c75c
        return self._install
Packit Service 21c75c
Packit Service 21c75c
    @install.setter
Packit Service 21c75c
    def install(self, value):
Packit Service 21c75c
        self._set_value(self._install, value)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def install_opt(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        Packages to be installed with strict=False - they will be
Packit Service 21c75c
        skipped if they cannot be installed
Packit Service 21c75c
        """
Packit Service 21c75c
        return self._install_opt
Packit Service 21c75c
Packit Service 21c75c
    @install_opt.setter
Packit Service 21c75c
    def install_opt(self, value):
Packit Service 21c75c
        self._set_value(self._install_opt, value)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def remove(self):
Packit Service 21c75c
        return self._remove
Packit Service 21c75c
Packit Service 21c75c
    @remove.setter
Packit Service 21c75c
    def remove(self, value):
Packit Service 21c75c
        self._set_value(self._remove, value)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def upgrade(self):
Packit Service 21c75c
        return self._upgrade
Packit Service 21c75c
Packit Service 21c75c
    @upgrade.setter
Packit Service 21c75c
    def upgrade(self, value):
Packit Service 21c75c
        self._set_value(self._upgrade, value)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class Solver(object):
Packit Service 21c75c
    def __init__(self, history, comps, reason_fn):
Packit Service 21c75c
        self.history = history
Packit Service 21c75c
        self.comps = comps
Packit Service 21c75c
        self._reason_fn = reason_fn
Packit Service 21c75c
Packit Service 21c75c
    @staticmethod
Packit Service 21c75c
    def _mandatory_group_set(env):
Packit Service 21c75c
        return {grp.id for grp in env.mandatory_groups}
Packit Service 21c75c
Packit Service 21c75c
    @staticmethod
Packit Service 21c75c
    def _full_package_set(grp):
Packit Service 21c75c
        return {pkg.getName() for pkg in grp.mandatory_packages +
Packit Service 21c75c
                grp.default_packages + grp.optional_packages +
Packit Service 21c75c
                grp.conditional_packages}
Packit Service 21c75c
Packit Service 21c75c
    @staticmethod
Packit Service 21c75c
    def _pkgs_of_type(group, pkg_types, exclude=[]):
Packit Service 21c75c
        def filter(pkgs):
Packit Service 21c75c
            return [pkg for pkg in pkgs
Packit Service 21c75c
                    if pkg.name not in exclude]
Packit Service 21c75c
Packit Service 21c75c
        pkgs = set()
Packit Service 21c75c
        if pkg_types & MANDATORY:
Packit Service 21c75c
            pkgs.update(filter(group.mandatory_packages))
Packit Service 21c75c
        if pkg_types & DEFAULT:
Packit Service 21c75c
            pkgs.update(filter(group.default_packages))
Packit Service 21c75c
        if pkg_types & OPTIONAL:
Packit Service 21c75c
            pkgs.update(filter(group.optional_packages))
Packit Service 21c75c
        if pkg_types & CONDITIONAL:
Packit Service 21c75c
            pkgs.update(filter(group.conditional_packages))
Packit Service 21c75c
        return pkgs
Packit Service 21c75c
Packit Service 21c75c
    def _removable_pkg(self, pkg_name):
Packit Service 21c75c
        assert dnf.util.is_string_type(pkg_name)
Packit Service 21c75c
        return self.history.group.is_removable_pkg(pkg_name)
Packit Service 21c75c
Packit Service 21c75c
    def _removable_grp(self, group_id):
Packit Service 21c75c
        assert dnf.util.is_string_type(group_id)
Packit Service 21c75c
        return self.history.env.is_removable_group(group_id)
Packit Service 21c75c
Packit Service 21c75c
    def _environment_install(self, env_id, pkg_types, exclude, strict=True, exclude_groups=None):
Packit Service 21c75c
        assert dnf.util.is_string_type(env_id)
Packit Service 21c75c
        comps_env = self.comps._environment_by_id(env_id)
Packit Service 21c75c
        if not comps_env:
Packit Service 21c75c
            raise CompsError(_("Environment id '%s' does not exist.") % ucd(env_id))
Packit Service 21c75c
Packit Service 21c75c
        swdb_env = self.history.env.new(env_id, comps_env.name, comps_env.ui_name, pkg_types)
Packit Service 21c75c
        self.history.env.install(swdb_env)
Packit Service 21c75c
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        for comps_group in comps_env.mandatory_groups:
Packit Service 21c75c
            if exclude_groups and comps_group.id in exclude_groups:
Packit Service 21c75c
                continue
Packit Service 21c75c
            trans += self._group_install(comps_group.id, pkg_types, exclude, strict)
Packit Service 21c75c
            swdb_env.addGroup(comps_group.id, True, MANDATORY)
Packit Service 21c75c
Packit Service 21c75c
        for comps_group in comps_env.optional_groups:
Packit Service 21c75c
            if exclude_groups and comps_group.id in exclude_groups:
Packit Service 21c75c
                continue
Packit Service 21c75c
            swdb_env.addGroup(comps_group.id, False, OPTIONAL)
Packit Service 21c75c
            # TODO: if a group is already installed, mark it as installed?
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _environment_remove(self, env_id):
Packit Service 21c75c
        assert dnf.util.is_string_type(env_id) is True
Packit Service 21c75c
        swdb_env = self.history.env.get(env_id)
Packit Service 21c75c
        if not swdb_env:
Packit Service 21c75c
            raise CompsError(_("Environment id '%s' is not installed.") % env_id)
Packit Service 21c75c
Packit Service 21c75c
        self.history.env.remove(swdb_env)
Packit Service 21c75c
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        group_ids = set([i.getGroupId() for i in swdb_env.getGroups()])
Packit Service 21c75c
        for group_id in group_ids:
Packit Service 21c75c
            if not self._removable_grp(group_id):
Packit Service 21c75c
                continue
Packit Service 21c75c
            trans += self._group_remove(group_id)
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _environment_upgrade(self, env_id):
Packit Service 21c75c
        assert dnf.util.is_string_type(env_id)
Packit Service 21c75c
        comps_env = self.comps._environment_by_id(env_id)
Packit Service 21c75c
        swdb_env = self.history.env.get(env_id)
Packit Service 21c75c
        if not swdb_env:
Packit Service 21c75c
            raise CompsError(_("Environment '%s' is not installed.") % env_id)
Packit Service 21c75c
        if not comps_env:
Packit Service 21c75c
            raise CompsError(_("Environment '%s' is not available.") % env_id)
Packit Service 21c75c
Packit Service 21c75c
        old_set = set([i.getGroupId() for i in swdb_env.getGroups()])
Packit Service 21c75c
        pkg_types = swdb_env.getPackageTypes()
Packit Service 21c75c
Packit Service 21c75c
        # create a new record for current transaction
Packit Service 21c75c
        swdb_env = self.history.env.new(comps_env.id, comps_env.name, comps_env.ui_name, pkg_types)
Packit Service 21c75c
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        for comps_group in comps_env.mandatory_groups:
Packit Service 21c75c
            if comps_group.id in old_set:
Packit Service 21c75c
                if self.history.group.get(comps_group.id):
Packit Service 21c75c
                    # upgrade installed group
Packit Service 21c75c
                    trans += self._group_upgrade(comps_group.id)
Packit Service 21c75c
            else:
Packit Service 21c75c
                # install new group
Packit Service 21c75c
                trans += self._group_install(comps_group.id, pkg_types)
Packit Service 21c75c
            swdb_env.addGroup(comps_group.id, True, MANDATORY)
Packit Service 21c75c
Packit Service 21c75c
        for comps_group in comps_env.optional_groups:
Packit Service 21c75c
            if comps_group.id in old_set and self.history.group.get(comps_group.id):
Packit Service 21c75c
                # upgrade installed group
Packit Service 21c75c
                trans += self._group_upgrade(comps_group.id)
Packit Service 21c75c
            swdb_env.addGroup(comps_group.id, False, OPTIONAL)
Packit Service 21c75c
            # TODO: if a group is already installed, mark it as installed?
Packit Service 21c75c
        self.history.env.upgrade(swdb_env)
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _group_install(self, group_id, pkg_types, exclude=None, strict=True, exclude_groups=None):
Packit Service 21c75c
        assert dnf.util.is_string_type(group_id)
Packit Service 21c75c
        comps_group = self.comps._group_by_id(group_id)
Packit Service 21c75c
        if not comps_group:
Packit Service 21c75c
            raise CompsError(_("Group id '%s' does not exist.") % ucd(group_id))
Packit Service 21c75c
Packit Service 21c75c
        swdb_group = self.history.group.new(group_id, comps_group.name, comps_group.ui_name, pkg_types)
Packit Service 21c75c
        for i in comps_group.packages_iter():
Packit Service 21c75c
            swdb_group.addPackage(i.name, False, Package._OPT_MAP[i.type])
Packit Service 21c75c
        self.history.group.install(swdb_group)
Packit Service 21c75c
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        # TODO: remove exclude
Packit Service 21c75c
        if strict:
Packit Service 21c75c
            trans.install.update(self._pkgs_of_type(comps_group, pkg_types, exclude=[]))
Packit Service 21c75c
        else:
Packit Service 21c75c
            trans.install_opt.update(self._pkgs_of_type(comps_group, pkg_types, exclude=[]))
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _group_remove(self, group_id):
Packit Service 21c75c
        assert dnf.util.is_string_type(group_id)
Packit Service 21c75c
        swdb_group = self.history.group.get(group_id)
Packit Service 21c75c
        if not swdb_group:
Packit Service 21c75c
            raise CompsError(_("Module or Group '%s' is not installed.") % group_id)
Packit Service 21c75c
        self.history.group.remove(swdb_group)
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        trans.remove = {pkg for pkg in swdb_group.getPackages() if self._removable_pkg(pkg.getName())}
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _group_upgrade(self, group_id):
Packit Service 21c75c
        assert dnf.util.is_string_type(group_id)
Packit Service 21c75c
        comps_group = self.comps._group_by_id(group_id)
Packit Service 21c75c
        swdb_group = self.history.group.get(group_id)
Packit Service 21c75c
        exclude = []
Packit Service 21c75c
Packit Service 21c75c
        if not swdb_group:
Packit Service 21c75c
            argument = comps_group.ui_name if comps_group else group_id
Packit Service 21c75c
            raise CompsError(_("Module or Group '%s' is not installed.") % argument)
Packit Service 21c75c
        if not comps_group:
Packit Service 21c75c
            raise CompsError(_("Module or Group '%s' is not available.") % group_id)
Packit Service 21c75c
        pkg_types = swdb_group.getPackageTypes()
Packit Service 21c75c
        old_set = set([i.getName() for i in swdb_group.getPackages()])
Packit Service 21c75c
        new_set = self._pkgs_of_type(comps_group, pkg_types, exclude)
Packit Service 21c75c
Packit Service 21c75c
        # create a new record for current transaction
Packit Service 21c75c
        swdb_group = self.history.group.new(group_id, comps_group.name, comps_group.ui_name, pkg_types)
Packit Service 21c75c
        for i in comps_group.packages_iter():
Packit Service 21c75c
            swdb_group.addPackage(i.name, False, Package._OPT_MAP[i.type])
Packit Service 21c75c
        self.history.group.upgrade(swdb_group)
Packit Service 21c75c
Packit Service 21c75c
        trans = TransactionBunch()
Packit Service 21c75c
        trans.install = {pkg for pkg in new_set if pkg.name not in old_set}
Packit Service 21c75c
        trans.remove = {name for name in old_set
Packit Service 21c75c
                        if name not in [pkg.name for pkg in new_set]}
Packit Service 21c75c
        trans.upgrade = {pkg for pkg in new_set if pkg.name in old_set}
Packit Service 21c75c
        return trans
Packit Service 21c75c
Packit Service 21c75c
    def _exclude_packages_from_installed_groups(self, base):
Packit Service 21c75c
        for group in self.persistor.groups:
Packit Service 21c75c
            p_grp = self.persistor.group(group)
Packit Service 21c75c
            if p_grp.installed:
Packit Service 21c75c
                installed_pkg_names = \
Packit Service 21c75c
                    set(p_grp.full_list) - set(p_grp.pkg_exclude)
Packit Service 21c75c
                installed_pkgs = base.sack.query().installed().filterm(name=installed_pkg_names)
Packit Service 21c75c
                for pkg in installed_pkgs:
Packit Service 21c75c
                    base._goal.install(pkg)