This fixes a case where extra packages are pulled in. The scenario is: * there's a lookaside repo which contains group G which has package P * we want to pull group G into the compose, but our definition of G does not contain P * lookaside repo does not contain package P * current package set has P DNF depsolver will then merge the two definitions and try to get all the packages. For most cases this is not a problem, since the package is in the lookaside repo and will not be pulled into the compose. But in the example above since P is not in lookaside, Pungi will put it into current compose. This is also ugly in the depsolving log says it includes package P because it was in input. But checking comps file does not show it. The result of this change is that some packages might disappear from current composes. This can only happen if there is a lookaside which defines groups with similar IDs, which should be very rare. In cases where this would be a problem the fix is to explicitly add wanted packages either to comps or to additional packages. Fixes: https://pagure.io/pungi/issue/978 Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
# -*- 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/>.
|
|
|
|
|
|
# TODO: remove all DNF hacks, possibly this whole file
|
|
|
|
|
|
from distutils.version import LooseVersion
|
|
import os
|
|
|
|
import dnf
|
|
|
|
import dnf.conf
|
|
import dnf.repo
|
|
import dnf.sack
|
|
|
|
import pungi.arch
|
|
|
|
try:
|
|
import dnf.rpm as dnf_arch
|
|
except ImportError:
|
|
import dnf.arch as dnf_arch
|
|
|
|
|
|
class Conf(dnf.conf.Conf):
|
|
# This is only modified to get our custom Substitutions class in.
|
|
def __init__(self, arch, *args, **kwargs):
|
|
super(Conf, self).__init__(*args, **kwargs)
|
|
self.substitutions = Substitutions(arch)
|
|
|
|
|
|
class Substitutions(dict):
|
|
# DNF version of Substitutions detects host arch. We don't want that.
|
|
def __init__(self, arch):
|
|
super(Substitutions, self).__init__()
|
|
self['arch'] = arch
|
|
self['basearch'] = dnf_arch.basearch(arch)
|
|
|
|
|
|
class DnfWrapper(dnf.Base):
|
|
def __init__(self, *args, **kwargs):
|
|
super(DnfWrapper, self).__init__(*args, **kwargs)
|
|
self.arch_wrapper = ArchWrapper(self.conf.substitutions["arch"])
|
|
self.comps_wrapper = CompsWrapper(self)
|
|
|
|
def add_repo(
|
|
self, repoid, baseurl=None, mirrorlist=None, enablegroups=True, lookaside=False
|
|
):
|
|
if "://" not in baseurl:
|
|
baseurl = "file://%s" % os.path.abspath(baseurl)
|
|
if LooseVersion(dnf.__version__) < LooseVersion("2.0.0"):
|
|
repo = dnf.repo.Repo(repoid, self.conf.cachedir)
|
|
else:
|
|
repo = dnf.repo.Repo(repoid, self.conf)
|
|
repo.baseurl = baseurl
|
|
repo.mirrorlist = mirrorlist
|
|
repo.enablegroups = enablegroups
|
|
repo.enable()
|
|
self.repos.add(repo)
|
|
repo.priority = 10 if lookaside else 20
|
|
|
|
|
|
class CompsWrapper(object):
|
|
def __init__(self, dnf_obj):
|
|
self.dnf = dnf_obj
|
|
|
|
def __getitem__(self, name):
|
|
return self.groups[name]
|
|
|
|
@property
|
|
def comps(self):
|
|
return self.dnf.comps
|
|
|
|
@property
|
|
def groups(self):
|
|
result = {}
|
|
for i in self.comps.groups:
|
|
result[i.id] = i
|
|
return result
|
|
|
|
def get_packages_from_group(self, group_id, include_default=True, include_optional=True, include_conditional=True):
|
|
packages = []
|
|
conditional = []
|
|
|
|
group = self.groups[group_id]
|
|
|
|
# add mandatory packages
|
|
packages.extend([i.name for i in group.mandatory_packages])
|
|
|
|
# add default packages
|
|
if include_default:
|
|
packages.extend([i.name for i in group.default_packages])
|
|
|
|
# add optional packages
|
|
if include_optional:
|
|
packages.extend([i.name for i in group.optional_packages])
|
|
|
|
for package in group.conditional_packages:
|
|
conditional.append({"name": package.requires, "install": package.name})
|
|
|
|
return packages, conditional
|
|
|
|
def get_comps_packages(self, groups, exclude_groups):
|
|
packages = set()
|
|
conditional = []
|
|
|
|
if isinstance(groups, list):
|
|
groups = dict([(i, 1) for i in groups])
|
|
|
|
for group_id, group_include in sorted(groups.items()):
|
|
if group_id in exclude_groups:
|
|
continue
|
|
|
|
include_default = group_include in (1, 2)
|
|
include_optional = group_include in (2, )
|
|
include_conditional = True
|
|
pkgs, cond = self.get_packages_from_group(group_id, include_default, include_optional, include_conditional)
|
|
packages.update(pkgs)
|
|
for i in cond:
|
|
if i not in conditional:
|
|
conditional.append(i)
|
|
return list(packages), conditional
|
|
|
|
def get_langpacks(self):
|
|
result = []
|
|
for name, install in self.comps._i.langpacks.items():
|
|
result.append({"name": name, "install": install})
|
|
return result
|
|
|
|
|
|
class ArchWrapper(object):
|
|
def __init__(self, arch):
|
|
self.base_arch = dnf_arch.basearch(arch)
|
|
self.all_arches = pungi.arch.get_valid_arches(self.base_arch, multilib=True, add_noarch=True)
|
|
self.native_arches = pungi.arch.get_valid_arches(self.base_arch, multilib=False, add_noarch=True)
|
|
self.multilib_arches = pungi.arch.get_valid_multilib_arches(self.base_arch)
|
|
self.source_arches = ["src", "nosrc"]
|