diff options
Diffstat (limited to 'multiarchanalyze.py')
-rwxr-xr-x | multiarchanalyze.py | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/multiarchanalyze.py b/multiarchanalyze.py new file mode 100755 index 0000000..904ade6 --- /dev/null +++ b/multiarchanalyze.py @@ -0,0 +1,196 @@ +#!/usr/bin/python + +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(left, right), max(left, right)) + 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.safe_dump(dict(format="multiarch-hints-1.0", hints=hints), + default_flow_style=False, + width=1000, + stream=sys.stdout) + +if __name__ == "__main__": + main() |