def auto_decruft_suite(suite_name, suite_id, session, dryrun, debug): """Run the auto-decrufter on a given suite @type suite_name: string @param suite_name: The name of the suite to remove from @type suite_id: int @param suite_id: The id of the suite denoted by suite_name @type session: SQLA Session @param session: The database session in use @type dryrun: bool @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(six.iteritems(removal_request)) 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(six.iterkeys(groups))))) # 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 six.itervalues(groups): full_removal_request.extend( six.iteritems(group_info["removal_request"])) 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"] # Embed the -R just in case someone wants to run it manually later print( ' dak rm -m "{message}" -s {suite} -a {architectures} -p -R -b {packages}' .format( message=message, suite=suite_name, architectures=",".join(archs), packages=" ".join(pkgs), )) print() print( "Note: The removals may be interdependent. A non-breaking result may require the execution of all" ) print("of the removals") else: remove_groups(six.itervalues(groups), suite_id, suite_name, session)
def auto_decruft_suite(suite_name, suite_id, session, dryrun, debug): """Run the auto-decrufter on a given suite @type suite_name: string @param suite_name: The name of the suite to remove from @type suite_id: int @param suite_id: The id of the suite denoted by suite_name @type session: SQLA Session @param session: The database session in use @type dryrun: bool @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"] # Embed the -R just in case someone wants to run it manually later print(' dak rm -m "{message}" -s {suite} -a {architectures} -p -R -b {packages}'.format( message=message, suite=suite_name, architectures=",".join(archs), packages=" ".join(pkgs), )) print() print("Note: The removals may be interdependent. A non-breaking result may require the execution of all") print("of the removals") else: remove_groups(groups.itervalues(), suite_id, suite_name, session)