+ @param dryrun: If True, just print the actions rather than actually doing them
+
+ @type debug: bool
+ @param debug: If True, print some extra information
+ """
+ all_architectures = [a.arch_string for a in get_suite_architectures(suite_name)]
+ pkg_arch2groups = defaultdict(set)
+ group_order = []
+ groups = {}
+ full_removal_request = []
+ group_generator = chain(
+ compute_sourceless_groups(suite_id, session),
+ compute_nbs_groups(suite_id, suite_name, session)
+ )
+ for group in group_generator:
+ group_name = group["name"]
+ pkgs = group["packages"]
+ affected_archs = group["architectures"]
+ # If we remove an arch:all package, then the breakage can occur on any
+ # of the architectures.
+ if "all" in affected_archs:
+ affected_archs = all_architectures
+ for pkg_arch in product(pkgs, affected_archs):
+ pkg_arch2groups[pkg_arch].add(group_name)
+ if group_name not in groups:
+ groups[group_name] = group
+ group_order.append(group_name)
+ else:
+ # This case usually happens when versions differ between architectures...
+ if debug:
+ print "N: Merging group %s" % (group_name)
+ groups[group_name] = merge_group(groups[group_name], group)
+
+ for group_name in group_order:
+ removal_request = groups[group_name]["removal_request"]
+ full_removal_request.extend(removal_request.iteritems())
+
+ if not groups:
+ if debug:
+ print "N: Found no candidates"
+ return
+
+ if debug:
+ print "N: Considering to remove the following packages:"
+ for group_name in sorted(groups):
+ group_info = groups[group_name]
+ pkgs = group_info["packages"]
+ archs = group_info["architectures"]
+ print "N: * %s: %s [%s]" % (group_name, ", ".join(pkgs), " ".join(archs))
+
+ if debug:
+ print "N: Compiling ReverseDependencyChecker (RDC) - please hold ..."
+ rdc = ReverseDependencyChecker(session, suite_name)
+ if debug:
+ print "N: Computing initial breakage..."
+
+ breakage = rdc.check_reverse_depends(full_removal_request)
+ while breakage:
+ by_breakers = [(len(breakage[x]), x, breakage[x]) for x in breakage]
+ by_breakers.sort(reverse=True)
+ if debug:
+ print "N: - Removal would break %s (package, architecture)-pairs" % (len(breakage))
+ print "N: - full breakage:"
+ for _, breaker, broken in by_breakers:
+ bname = "%s/%s" % breaker
+ broken_str = ", ".join("%s/%s" % b for b in sorted(broken))
+ print "N: * %s => %s" % (bname, broken_str)
+
+ averted_breakage = set()
+
+ for _, package_arch, breakage in by_breakers:
+ if breakage <= averted_breakage:
+ # We already avoided this break
+ continue
+ guilty_groups = pkg_arch2groups[package_arch]
+
+ if not guilty_groups:
+ utils.fubar("Cannot figure what group provided %s" % str(package_arch))
+
+ if debug:
+ # Only output it, if it truly a new group being discarded
+ # - a group can reach this part multiple times, if it breaks things on
+ # more than one architecture. This being rather common in fact.
+ already_discard = True
+ if any(group_name for group_name in guilty_groups if group_name in groups):
+ already_discard = False
+
+ if not already_discard:
+ avoided = sorted(breakage - averted_breakage)
+ print "N: - skipping removal of %s (breakage: %s)" % (", ".join(sorted(guilty_groups)), str(avoided))
+
+ averted_breakage |= breakage
+ for group_name in guilty_groups:
+ if group_name in groups:
+ del groups[group_name]
+
+ if not groups:
+ if debug:
+ print "N: Nothing left to remove"
+ return
+
+ if debug:
+ print "N: Now considering to remove: %s" % str(", ".join(sorted(groups.iterkeys())))
+
+ # Rebuild the removal request with the remaining groups and off
+ # we go to (not) break the world once more time
+ full_removal_request = []
+ for group_info in groups.itervalues():
+ full_removal_request.extend(group_info["removal_request"].iteritems())
+ breakage = rdc.check_reverse_depends(full_removal_request)
+
+ if debug:
+ print "N: Removal looks good"
+
+ if dryrun:
+ print "Would remove the equivalent of:"
+ for group_name in group_order:
+ if group_name not in groups:
+ continue
+ group_info = groups[group_name]
+ pkgs = group_info["packages"]
+ archs = group_info["architectures"]
+ message = group_info["message"]