#!/usr/bin/python3 import argparse import os.path import re import sqlite3 import sys import yaml from dedup.utils import fetchiter def bipartitions(edges): """ @type edges: {T: set(T)} @rtype: [(set(T), set(T))] """ todo = set(edges.keys()) result = [] colors = {} while todo: node = todo.pop() colors[node] = False partition = (set((node,)), set()) todo2 = set((node,)) while todo2: partnode = todo2.pop() for nextnode in edges[partnode]: try: if colors[nextnode] == colors[partnode]: raise ValueError("not bipartite") except KeyError: colors[nextnode] = not colors[partnode] partition[colors[nextnode]].add(nextnode) todo2.add(nextnode) todo.remove(nextnode) result.append(partition) return result def show_archset(archs, limit=3): """ @type archs: set(str) @rtype: str """ archs = sorted(set(archs)) if len(archs) <= limit: return ", ".join(archs) assert limit > 2 limit = limit - 1 return "%s, and %d more" % (", ".join(archs[:limit]), len(archs) - limit) def show_combinations(combinations): edges = {} for left, right in combinations: edges.setdefault(left, set()).add(right) edges.setdefault(right, set()).add(left) # safety if len(edges) == 2: return "%s <-> %s" % (min(edges), max(edges)) try: partitions = bipartitions(edges) except ValueError: pass else: if len(partitions) == 1: return " <-> ".join(show_archset(archs, 4) for archs in sorted(partitions[0], key=len)) else: return "%d bipartitions of %s" % (len(partitions), show_archset(edges.keys())) if all(len(outedges) == len(edges) - 1 for outedges in edges.values()): return "any two of " + show_archset(edges.keys(), limit=5) return "%s with %d combinations" % (show_archset(edges.keys()), len(combinations)) def show_files(filenames): if len(filenames) == 1: return next(iter(filenames)) prefix = os.path.commonprefix(filenames) if prefix not in ('', '/'): return "%d files starting with %s" % (len(filenames), prefix) return "%d files" % len(filenames) def sqlite_regexp(pattern, value): return re.match(pattern, value) is not None def main(): parser = argparse.ArgumentParser() parser.add_argument("-d", "--database", action="store", default="test.sqlite3", help="path to the sqlite3 database file") args = parser.parse_args() hints = [] db = sqlite3.connect(args.database) db.create_function("REGEXP", 2, sqlite_regexp) cur = db.cursor() cur.execute("SELECT name, architecture1, architecture2, filename FROM masame_conflict;") same_conflicts = {} for package, arch1, arch2, filename in fetchiter(cur): if filename.startswith("./"): filename = filename[1:] conflicts = same_conflicts.setdefault(package, {}) conflicts.setdefault(filename, set()).add((arch1, arch2)) for package, conflicts in sorted(same_conflicts.items()): all_combinations = set() for combinations in conflicts.values(): all_combinations.update(combinations) desc = "%s conflicts on %s on %s" % \ (package, show_files(list(conflicts.keys())), show_combinations(all_combinations)) hints.append(dict( binary=package, description=desc, link="https://wiki.debian.org/MultiArch/Hints#file-conflict", severity="high")) cur.execute("SELECT name FROM maforeign_candidate ORDER BY name;") for name, in fetchiter(cur): hints.append(dict( binary=name, description="%s could be marked Multi-Arch: foreign" % name, link="https://wiki.debian.org/MultiArch/Hints#ma-foreign", severity="rdeps")) cur.execute("SELECT name FROM archall_candidate ORDER BY name;") archall_suggests = set() for name, in fetchiter(cur): archall_suggests.add(name) desc = "%s could be converted to Architecture: all and marked Multi-Arch: foreign" % \ name hints.append(dict( binary=name, description=desc, link="https://wiki.debian.org/MultiArch/Hints#arch-all", severity="low")) cur.execute("SELECT name FROM masame_candidate ORDER BY name;") for name, in fetchiter(cur): if name in archall_suggests: continue hints.append(dict( binary=name, description="%s could be marked Multi-Arch: same" % name, link="https://wiki.debian.org/MultiArch/Hints#ma-same", severity="normal")) cur.execute("SELECT depender, dependee FROM colonany_candidate ORDER BY depender;") for depender, dependee in fetchiter(cur): desc = "%s could have its dependency on %s annotated with :any" % \ (depender, dependee) hints.append(dict( binary=depender, description=desc, link="https://wiki.debian.org/MultiArch/Hints#dep-any", severity="normal")) cur.execute("SELECT name FROM maforeign_library ORDER BY name;") for name, in fetchiter(cur): hints.append(dict( binary=name, description="%s is wrongly marked Multi-Arch: foreign" % name, link="https://wiki.debian.org/MultiArch/Hints#ma-foreign-library", severity="high")) for hint in hints: if "source" not in hint: cur.execute("SELECT distinct(source) FROM package WHERE name = ?;", (hint["binary"],)) row = cur.fetchone() if row and row[0] and not cur.fetchone(): hint["source"] = row[0] if "version" not in hint: cur.execute("SELECT distinct(version) FROM package WHERE name = ?;", (hint["binary"],)) row = cur.fetchone() if row and row[0] and not cur.fetchone(): hint["version"] = row[0] if hint.get("severity") == "rdeps": cur.execute("SELECT count(*) FROM depends WHERE dependee = ?;", (hint["binary"],)) rdeps = cur.fetchone()[0] if rdeps >= 50: hint["severity"] = "high" elif rdeps: hint["severity"] = "normal" else: hint["severity"] = "low" yaml.dump( dict(format="multiarch-hints-1.0", hints=hints), default_flow_style=False, Dumper=yaml.CSafeDumper, stream=sys.stdout, width=1000, ) if __name__ == "__main__": main()