Index: parallel_emerge |
diff --git a/parallel_emerge b/parallel_emerge |
index 7e88516c0180c73f530ecf5002bb50f3c593c912..f9576f6e7568fb11159232fa2ce9add9971e5293 100755 |
--- a/parallel_emerge |
+++ b/parallel_emerge |
@@ -642,39 +642,44 @@ class DepGraphGenerator(object): |
"""Find cycles in the dependency tree. |
Returns: |
- Dict of packages involved in cyclic dependencies, mapping each package |
- to a list of the cycles the package is involved in. |
+ A dict mapping cyclic packages to a dict of the deps that cause |
+ cycles. For each dep that causes cycles, it returns an example |
+ traversal of the graph that shows the cycle. |
""" |
- def FindCyclesAtNode(pkg, cycles, unresolved): |
+ def FindCyclesAtNode(pkg, cycles, unresolved, resolved): |
"""Find cycles in cyclic dependencies starting at specified package. |
Args: |
pkg: Package identifier. |
- cycles: Set of cycles so far. |
+ cycles: A dict mapping cyclic packages to a dict of the deps that |
+ cause cycles. For each dep that causes cycles, it returns an |
+ example traversal of the graph that shows the cycle. |
unresolved: Nodes that have been visited but are not fully processed. |
+ resolved: Nodes that have been visited and are fully processed. |
""" |
+ pkg_cycles = cycles.get(pkg) |
+ if pkg in resolved and not pkg_cycles: |
+ # If we already looked at this package, and found no cyclic |
+ # dependencies, we can stop now. |
+ return |
unresolved.append(pkg) |
- mycycles = cycles.get(pkg) |
- if mycycles: |
- mycycles = mycycles.get("pkgs") |
for dep in deps_map[pkg]["needs"]: |
- if mycycles and dep in mycycles: |
- continue |
- elif dep in unresolved: |
+ if dep in unresolved: |
idx = unresolved.index(dep) |
mycycle = unresolved[idx:] + [dep] |
- for cycle_pkg in mycycle: |
- info = cycles.setdefault(cycle_pkg, {}) |
- info.setdefault("pkgs", set()).update(mycycle) |
- info.setdefault("cycles", []).append(mycycle) |
- else: |
- FindCyclesAtNode(dep, cycles, unresolved) |
+ for i in range(len(mycycle) - 1): |
+ pkg1, pkg2 = mycycle[i], mycycle[i+1] |
+ cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle) |
+ elif not pkg_cycles or dep not in pkg_cycles: |
+ # Looks like we haven't seen this edge before. |
+ FindCyclesAtNode(dep, cycles, unresolved, resolved) |
unresolved.pop() |
+ resolved.add(pkg) |
- cycles, unresolved = {}, [] |
+ cycles, unresolved, resolved = {}, [], set() |
for pkg in deps_map: |
- FindCyclesAtNode(pkg, cycles, unresolved) |
+ FindCyclesAtNode(pkg, cycles, unresolved, resolved) |
return cycles |
def RemoveInstalledPackages(): |
@@ -736,6 +741,31 @@ class DepGraphGenerator(object): |
target_needs.pop(target, None) |
del deps_map[pkg] |
+ def PrintCycleBreak(basedep, dep, mycycle): |
+ """Print details about a cycle that we are planning on breaking. |
+ |
+ We are breaking a cycle where dep needs basedep. mycycle is an |
+ example cycle which contains dep -> basedep.""" |
+ |
+ # If it's an optional dependency, there's no need to spam the user with |
+ # warning messages. |
+ needs = deps_map[dep]["needs"] |
+ depinfo = needs.get(basedep, "deleted") |
+ if depinfo == "optional": |
+ return |
+ |
+ # Notify the user that we're breaking a cycle. |
+ print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo) |
+ |
+ # Show cycle. |
+ for i in range(len(mycycle) - 1): |
+ pkg1, pkg2 = mycycle[i], mycycle[i+1] |
+ needs = deps_map[pkg1]["needs"] |
+ depinfo = needs.get(pkg2, "deleted") |
+ if pkg1 == dep and pkg2 == basedep: |
+ depinfo = depinfo + ", deleting" |
+ print " %s -> %s (%s)" % (pkg1, pkg2, depinfo) |
+ |
def SanitizeTree(cycles): |
"""Remove circular dependencies. |
@@ -752,25 +782,12 @@ class DepGraphGenerator(object): |
package to a list of the cycles the package is involved in. Produced |
by FindCycles(). |
""" |
- for basedep, cycle_info in cycles.iteritems(): |
- for mycycle in cycle_info["cycles"]: |
- info = [] |
- broken = False |
- for i in range(len(mycycle) - 1): |
- pkg1, pkg2 = mycycle[i], mycycle[i+1] |
- needs = deps_map[pkg1]["needs"] |
- depinfo = needs.get(pkg2, "deleted") |
- bad = False |
- if (deps_info[pkg1]["idx"] >= deps_info[pkg2]["idx"] and |
- depinfo != "deleted"): |
- depinfo = depinfo + ", deleting" |
- broken = True |
- del deps_map[pkg1]["needs"][pkg2] |
- deps_map[pkg2]["provides"].remove(pkg1) |
- info.append(" %s -> %s (%s)" % (pkg1, pkg2, depinfo)) |
- if broken: |
- print "Breaking cycle:" |
- print "\n".join(info) |
+ for dep, mycycles in cycles.iteritems(): |
+ for basedep, mycycle in mycycles.iteritems(): |
+ if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]: |
+ PrintCycleBreak(basedep, dep, mycycle) |
+ del deps_map[dep]["needs"][basedep] |
+ deps_map[basedep]["provides"].remove(dep) |
def AddSecretDeps(): |
"""Find these tagged packages and add extra dependencies. |