Skip to content

Commit db2ce25

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

1 file changed

Lines changed: 73 additions & 2 deletions

File tree

src/pkgcheck/checks/visibility.py

Lines changed: 73 additions & 2 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:
@@ -465,3 +465,74 @@ def process_depset(self, pkg, attr, depset, edepset, profiles):
465465
failures.update(required)
466466
if failures:
467467
yield profile, failures
468+
469+
470+
class RdependCycle(results.VersionResult, results.Warning):
471+
def __init__(self, cycle, **kwargs):
472+
super().__init__(**kwargs)
473+
self.cycle = cycle
474+
475+
@property
476+
def desc(self):
477+
return f"cycle detected: {' -> '.join(self.cycle)}"
478+
479+
480+
class RdependCycleCheck(RepoCheck, OptionalCheck):
481+
_source = sources.PackageRepoSource
482+
known_results = frozenset({RdependCycle})
483+
484+
def __init__(self, options, **kwargs):
485+
super().__init__(options, **kwargs)
486+
self.visited_packages: dict[str, frozenset[str]] = {}
487+
self.repo = self.options.target_repo
488+
self.no_cycle = set()
489+
490+
def _verify_dfs(self, key: str, path: list[str], visited: set[str]):
491+
if key in path:
492+
path.append(key)
493+
return path
494+
assert key in self.visited_packages
495+
496+
visited.add(key)
497+
path.append(key)
498+
for dep in self.visited_packages[key] - self.no_cycle:
499+
if cycle := self._verify_dfs(dep, path, visited):
500+
return cycle
501+
path.pop()
502+
self.no_cycle.add(key)
503+
return []
504+
505+
def _collect_deps_graph(self, pkgset):
506+
key = pkgset[0].key
507+
508+
if key in self.visited_packages:
509+
return
510+
511+
pkg_deps = {
512+
pkg: {dep.key for dep in pkg.rdepend if isinstance(dep, atom) and not dep.blocks}
513+
for pkg in pkgset
514+
}
515+
self.visited_packages[key] = all_deps = frozenset().union(*pkg_deps.values())
516+
if missing := all_deps - self.visited_packages.keys():
517+
for missing_key in missing:
518+
try:
519+
self._collect_deps_graph(self.repo.match(atom(missing_key)))
520+
except IndexError:
521+
self.visited_packages[missing_key] = frozenset()
522+
return pkg_deps
523+
524+
def feed(self, pkgset):
525+
key = pkgset[0].key
526+
527+
if key in self.visited_packages:
528+
pkg_deps = {
529+
pkg: {dep.key for dep in pkg.rdepend if isinstance(dep, atom) and not dep.blocks}
530+
for pkg in pkgset
531+
}
532+
else:
533+
pkg_deps = self._collect_deps_graph(pkgset)
534+
535+
for pkg in pkgset:
536+
for dep in pkg_deps[pkg]:
537+
if (cycle := self._verify_dfs(dep, [key], set())) and cycle[-1] == key:
538+
yield RdependCycle(cycle, pkg=pkg)

0 commit comments

Comments
 (0)