mmd_resolver: add support for streams exclusion

Also fix support for dependencies with empty streams list.

Signed-off-by: Igor Gnatenko <ignatenko@redhat.com>
This commit is contained in:
Igor Gnatenko
2018-03-07 18:12:42 +01:00
committed by mprahl
parent ebd6dba78f
commit 85af781920
2 changed files with 84 additions and 32 deletions

View File

@@ -46,18 +46,47 @@ class MMDResolver(object):
self.build_repo = self.pool.add_repo("build")
self.available_repo = self.pool.add_repo("available")
def _deps2reqs(self, deps):
pool = self.pool
rel_or_dep = lambda dep, op, rel: dep.Rel(op, rel) if dep is not None else rel
stream_dep = lambda n, s: pool.Dep("module(%s:%s)" % (n, s))
reqs = None
for deps in deps:
require = None
for name, streams in deps.items():
req_pos = req_neg = None
for stream in streams:
if stream.startswith("-"):
req_neg = rel_or_dep(req_neg, solv.REL_OR, stream_dep(name, stream[1:]))
else:
req_pos = rel_or_dep(req_pos, solv.REL_OR, stream_dep(name, stream))
req = pool.Dep("module(%s)" % name)
if req_pos is not None:
req = req.Rel(solv.REL_WITH, req_pos)
elif req_neg is not None:
req = req.Rel(solv.REL_WITHOUT, req_neg)
require = rel_or_dep(require, solv.REL_AND, req)
reqs = rel_or_dep(reqs, solv.REL_OR, require)
return reqs
def add_modules(self, mmd):
n, s, v, c = mmd.get_name(), mmd.get_stream(), mmd.get_version(), mmd.get_context()
pool = self.pool
normdeps = lambda mmd, fn: [{name: streams.get()
for name, streams in getattr(dep, fn)().items()}
for dep in mmd.get_dependencies()]
solvables = []
if c is not None:
# Built module
deps = mmd.get_dependencies()
if len(deps) > 1:
raise ValueError(
"The built module contains different runtime dependencies: %s" % mmd.dumps())
# $n:$s:$v:$c-$v.$a
solvable = self.available_repo.add_solvable()
@@ -74,17 +103,8 @@ class MMDResolver(object):
pool.Dep("module(%s:%s)" % (n, s)).Rel(
solv.REL_EQ, pool.Dep(str(v))))
if deps:
# Req: (module($on1:$os1) OR module($on2:$os2) OR …)
for name, streams in deps[0].get_requires().items():
requires = None
for stream in streams.get():
require = pool.Dep("module(%s:%s)" % (name, stream))
if requires is not None:
requires = requires.Rel(solv.REL_OR, require)
else:
requires = require
solvable.add_deparray(solv.SOLVABLE_REQUIRES, requires)
requires = self._deps2reqs(normdeps(mmd, "get_requires"))
solvable.add_deparray(solv.SOLVABLE_REQUIRES, requires)
# Con: module($n)
solvable.add_deparray(solv.SOLVABLE_CONFLICTS, pool.Dep("module(%s)" % n))
@@ -95,6 +115,7 @@ class MMDResolver(object):
# Context means two things:
# * Unique identifier
# * Offset for the dependency which was used
normalized_deps = normdeps(mmd, "get_buildrequires")
for c, deps in enumerate(mmd.get_dependencies()):
# $n:$s:$c-$v.src
solvable = self.build_repo.add_solvable()
@@ -102,16 +123,8 @@ class MMDResolver(object):
solvable.evr = str(v)
solvable.arch = "src"
# Req: (module($on1:$os1) OR module($on2:$os2) OR …)
for name, streams in deps.get_buildrequires().items():
requires = None
for stream in streams.get():
require = pool.Dep("module(%s:%s)" % (name, stream))
if requires:
requires = requires.Rel(solv.REL_OR, require)
else:
requires = require
solvable.add_deparray(solv.SOLVABLE_REQUIRES, requires)
requires = self._deps2reqs([normalized_deps[c]])
solvable.add_deparray(solv.SOLVABLE_REQUIRES, requires)
solvables.append(solvable)
@@ -139,8 +152,27 @@ class MMDResolver(object):
for src in solvables:
job = self.pool.Job(solv.Job.SOLVER_INSTALL | solv.Job.SOLVER_SOLVABLE, src.id)
requires = src.lookup_deparray(solv.SOLVABLE_REQUIRES)
if len(requires) != 1:
raise SystemError("Exactly one element should be in Requires: %s" % requires)
requires = requires[0]
src_alternatives = alternatives[src] = collections.OrderedDict()
for opt in itertools.product(*[self.pool.whatprovides(dep) for dep in requires]):
# TODO: replace this ugliest workaround ever with sane code of parsing rich deps.
# We need to split them because whatprovides() treats "and" same way as "or" which is
# not enough to generate combinations.
# Source solvables have Req: (X and Y and Z)
# Binary solvables have Req: ((X and Y) or (X and Z))
# They do use "or" within "and", so simple string split won't work for binary packages.
if src.arch != "src":
raise NotImplementedError
deps = str(requires).split(" and ")
if len(deps) > 1:
deps[0] = deps[0][1:]
deps[-1] = deps[-1][:-1]
deps = [self.pool.parserpmrichdep(dep) if dep.startswith("(") else self.pool.Dep(dep)
for dep in deps]
for opt in itertools.product(*[self.pool.whatprovides(dep) for dep in deps]):
log.debug("Testing %s with combination: %s", src, opt)
if policy == MMDResolverPolicy.All:
kfunc = s2nsvc

View File

@@ -23,9 +23,11 @@
# Written by Jan Kaluža <jkaluza@redhat.com>
# Igor Gnatenko <ignatenko@redhat.com>
import collections
import gi
gi.require_version("Modulemd", "1.0") # noqa
from gi.repository import Modulemd
import pytest
from module_build_service.mmd_resolver import MMDResolver
@@ -55,17 +57,17 @@ class TestMMDResolver:
version, context = version.split(":")
mmd.set_context(context)
add_requires = Modulemd.Dependencies.add_requires
requires_list = [requires]
else:
add_requires = Modulemd.Dependencies.add_buildrequires
if not isinstance(requires, list):
requires_list = [requires]
else:
requires_list = requires
mmd.set_version(int(version))
if not isinstance(requires, list):
requires = [requires]
else:
requires = requires
deps_list = []
for reqs in requires_list:
for reqs in requires:
deps = Modulemd.Dependencies()
for req_name, req_streams in reqs.items():
add_requires(deps, req_name, req_streams)
@@ -74,6 +76,24 @@ class TestMMDResolver:
return mmd
@pytest.mark.parametrize(
"deps, expected", (
([], "None"),
([{"x": []}], "module(x)"),
([{"x": ["1"]}], "(module(x) with module(x:1))"),
([{"x": ["-1"]}], "(module(x) without module(x:1))"),
([{"x": ["1", "2"]}], "(module(x) with (module(x:1) or module(x:2)))"),
([{"x": ["-1", "2"]}], "(module(x) with module(x:2))"),
([{"x": [], "y": []}], "(module(x) and module(y))"),
([{"x": []}, {"y": []}], "(module(x) or module(y))"),
)
)
def test_deps2reqs(self, deps, expected):
# Sort by keys here to avoid unordered dicts
deps = [collections.OrderedDict(sorted(dep.items())) for dep in deps]
reqs = self.mmd_resolver._deps2reqs(deps)
assert str(reqs) == expected
@classmethod
def _default_mmds(cls):
return [