Skip to content

Commit 3268908

Browse files
committed
[wip] cycle check
Resolves: #400 Signed-off-by: Arthur Zamarin <arthurzam@gentoo.org>
1 parent 5688029 commit 3268908

File tree

1 file changed

+72
-4
lines changed

1 file changed

+72
-4
lines changed

src/pkgcheck/checks/visibility.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from snakeoil.sequences import iflatten_func, iflatten_instance, stable_unique
88
from snakeoil.strings import pluralism
99

10-
from .. import addons, feeds, results
11-
from . import Check
10+
from .. import addons, feeds, results, sources
11+
from . import Check, OptionalCheck, RepoCheck
1212

1313

1414
class FakeConfigurable:
@@ -210,14 +210,14 @@ class VisibilityCheck(feeds.EvaluateDepSet, feeds.QueryCache, Check):
210210

211211
required_addons = (addons.profiles.ProfileAddon,)
212212
known_results = frozenset(
213-
[
213+
{
214214
VisibleVcsPkg,
215215
NonexistentDeps,
216216
UncheckableDep,
217217
NonsolvableDepsInStable,
218218
NonsolvableDepsInDev,
219219
NonsolvableDepsInExp,
220-
]
220+
}
221221
)
222222

223223
def __init__(self, *args, profile_addon):
@@ -386,3 +386,71 @@ def process_depset(self, pkg, attr, depset, edepset, profiles):
386386
failures.update(required)
387387
if failures:
388388
yield profile, failures
389+
390+
391+
class RdependCycle(results.VersionResult, results.Warning):
392+
def __init__(self, cycle, **kwargs):
393+
super().__init__(**kwargs)
394+
self.cycle = cycle
395+
396+
@property
397+
def desc(self):
398+
return f"cycle detected: {' -> '.join(self.cycle)}"
399+
400+
401+
class RdependCycleCheck(RepoCheck, OptionalCheck):
402+
_source = sources.PackageRepoSource
403+
known_results = frozenset({RdependCycle})
404+
405+
def __init__(self, options, **kwargs):
406+
super().__init__(options, **kwargs)
407+
self.visited_packages: dict[str, frozenset[str]] = {}
408+
self.repo = self.options.target_repo
409+
self.no_cycle = set()
410+
411+
def _verify_dfs(self, key: str, path: list[str], visited: set[str]):
412+
if key in path:
413+
path.append(key)
414+
return path
415+
assert key in self.visited_packages
416+
417+
visited.add(key)
418+
path.append(key)
419+
for dep in self.visited_packages[key] - self.no_cycle:
420+
if cycle := self._verify_dfs(dep, path, visited):
421+
return cycle
422+
path.pop()
423+
self.no_cycle.add(key)
424+
return []
425+
426+
def _collect_deps_graph(self, pkgset):
427+
key = pkgset[0].key
428+
429+
if key in self.visited_packages:
430+
return
431+
432+
pkg_deps = {
433+
pkg: {dep.key for dep in pkg.rdepend if isinstance(dep, atom) and not dep.blocks}
434+
for pkg in pkgset
435+
}
436+
self.visited_packages[key] = all_deps = frozenset().union(*pkg_deps.values())
437+
if missing := all_deps - self.visited_packages.keys():
438+
for missing_key in missing:
439+
self._collect_deps_graph(self.repo.match(atom(missing_key)))
440+
return pkg_deps
441+
442+
def feed(self, pkgset):
443+
key = pkgset[0].key
444+
445+
if key in self.visited_packages:
446+
pkg_deps = {
447+
pkg: {dep.key for dep in pkg.rdepend if isinstance(dep, atom) and not dep.blocks}
448+
for pkg in pkgset
449+
}
450+
else:
451+
pkg_deps = self._collect_deps_graph(pkgset)
452+
453+
for pkg in pkgset:
454+
for dep in pkg_deps[pkg]:
455+
if (cycle := self._verify_dfs(dep, [key], set())) and cycle[-1] == key:
456+
yield RdependCycle(cycle, pkg=pkg)

0 commit comments

Comments
 (0)