|
1 | 1 | from collections import defaultdict |
| 2 | +from itertools import groupby |
2 | 3 | from operator import attrgetter |
3 | 4 |
|
4 | 5 | from pkgcore.ebuild.atom import atom, transitive_use_atom |
@@ -487,52 +488,64 @@ def __init__(self, options, **kwargs): |
487 | 488 | self.repo = self.options.target_repo |
488 | 489 | self.no_cycle = set() |
489 | 490 |
|
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 |
| 491 | + def find_cycle(self, start_key: str): |
| 492 | + path: list[str] = [] |
| 493 | + visited: set[str] = set() |
495 | 494 |
|
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 [] |
| 495 | + def dfs(node): |
| 496 | + visited.add(node) |
| 497 | + path.append(node) |
504 | 498 |
|
505 | | - def _collect_deps_graph(self, pkgset): |
506 | | - key = pkgset[0].key |
| 499 | + for neighbor in sorted(self.visited_packages[node] - self.no_cycle): |
| 500 | + if neighbor not in visited: |
| 501 | + if result := dfs(neighbor): |
| 502 | + return result |
| 503 | + elif neighbor in path and neighbor == start_key: |
| 504 | + # Found a cycle that ends at the start node |
| 505 | + idx = path.index(start_key) |
| 506 | + return path[idx:] + [start_key] |
507 | 507 |
|
508 | | - if key in self.visited_packages: |
509 | | - return |
| 508 | + path.pop() |
| 509 | + self.no_cycle.add(node) |
| 510 | + return None |
| 511 | + |
| 512 | + return dfs(start_key) |
510 | 513 |
|
| 514 | + def _collect_deps_graph(self, key: str, pkgset): |
511 | 515 | pkg_deps = { |
512 | | - pkg: {dep.key for dep in pkg.rdepend if isinstance(dep, atom) and not dep.blocks} |
| 516 | + pkg: { |
| 517 | + f"{dep.key}:{dep.slot}" if dep.slot is not None else dep.key |
| 518 | + for dep in pkg.rdepend |
| 519 | + if isinstance(dep, atom) and not dep.blocks |
| 520 | + } |
513 | 521 | for pkg in pkgset |
514 | 522 | } |
| 523 | + |
| 524 | + if key in self.visited_packages: |
| 525 | + return pkg_deps |
| 526 | + |
515 | 527 | self.visited_packages[key] = all_deps = frozenset().union(*pkg_deps.values()) |
516 | 528 | if missing := all_deps - self.visited_packages.keys(): |
517 | 529 | for missing_key in missing: |
518 | 530 | try: |
519 | | - self._collect_deps_graph(self.repo.match(atom(missing_key))) |
520 | | - except IndexError: |
| 531 | + self._collect_deps_graph(missing_key, self.repo.match(atom(missing_key))) |
| 532 | + except IndexError: # NonexistentDeps, invalid dep |
521 | 533 | self.visited_packages[missing_key] = frozenset() |
522 | 534 | return pkg_deps |
523 | 535 |
|
524 | | - def feed(self, pkgset): |
525 | | - key = pkgset[0].key |
| 536 | + def _collect_graph_variants(self, pkgset): |
| 537 | + self._collect_deps_graph(key := pkgset[0].key, pkgset) |
| 538 | + yield key, pkgset |
526 | 539 |
|
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) |
| 540 | + def key_func(x): |
| 541 | + return f"{x.key}:{x.slot}" |
| 542 | + |
| 543 | + for key, pkgs in groupby(sorted(pkgset, key=key_func), key_func): |
| 544 | + self._collect_deps_graph(key, pkgs) |
| 545 | + yield key, pkgs |
534 | 546 |
|
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: |
| 547 | + def feed(self, pkgset): |
| 548 | + for key, pkg_deps in self._collect_graph_variants(pkgset): |
| 549 | + if (cycle := self.find_cycle(key)) and cycle[-1] == key: |
| 550 | + for pkg in pkg_deps: |
538 | 551 | yield RdependCycle(cycle, pkg=pkg) |
0 commit comments