summaryrefslogtreecommitdiff
path: root/multiarchanalyze.py
diff options
context:
space:
mode:
Diffstat (limited to 'multiarchanalyze.py')
-rwxr-xr-xmultiarchanalyze.py196
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()