Add cycles detection

This commit is contained in:
Alexander Lakhin 2014-02-17 14:07:16 +04:00
parent 6bdb49da9d
commit 183ad8fef6

View file

@ -6,7 +6,6 @@ import sys
import gettext import gettext
import argparse import argparse
import sqlite3 import sqlite3
import string
import rpm import rpm
import re import re
@ -591,6 +590,116 @@ SELECT srp.name, sp.nvra, tp.nvra, tp.repodir_id FROM packages sp, rpm_provides
return (result, len(result)) return (result, len(result))
class cycles(query_output):
title = 'Dependency cycles'
def find_all_cycles(self, graph):
"""
Find all cycles in the given graph.
This function will return a list of lists of nodes, which form cycles
in the graph or an empty list if no cycle exists.
"""
def dfs(node):
"""
Depth-first search subfunction.
"""
def find_cycle_to_ancestor(spanning_tree, node, ancestor):
"""
Find a cycle containing both node and ancestor.
"""
path = []
while (node != ancestor):
if node is None:
return []
path.append(node)
node = spanning_tree[node]
path.append(node)
path.reverse()
return path
visited.add(node)
# Explore recursively the connected component
if node not in graph:
return
for each in graph[node]:
if each not in visited:
spanning_tree[each] = node
dfs(each)
else:
if (spanning_tree[node] != each):
cycle = find_cycle_to_ancestor(spanning_tree, node,
each)
if cycle:
cycles.append(cycle)
visited = set() # List for marking visited and non-visited nodes
spanning_tree = {} # Spanning tree
cycles = []
# Algorithm outer-loop
for each in graph:
# Select a non-visited node
if each not in visited:
spanning_tree[each] = None
# Explore node's connected component
dfs(each)
return cycles
def get_data(self, repodir_id):
rows = self.dbc.execute("""
SELECT sp.id, tp.id, sp.nvra, tp.nvra, req.name
FROM packages sp
CROSS JOIN package_requires_res pqr ON sp.id = pqr.package_id
CROSS JOIN rpm_requires req ON pqr.requires_id = req.id
CROSS JOIN packages tp ON pqr.dep_package_id = tp.id
WHERE sp.repodir_id = ? AND tp.repodir_id = sp.repodir_id AND sp.id <> tp.id
ORDER BY sp.id, tp.id
""", [repodir_id]).fetchall()
pre_pkg_id = None
pre_dep_pkg_id = None
deps = []
graph = {}
pkg_names = {}
req_links = {}
for row in rows:
(pkg_id, dep_pkg_id) = (row[0], row[1])
pkg_names[row[0]] = row[2]
pkg_names[row[1]] = row[3]
if pre_pkg_id is not None and pre_pkg_id != pkg_id:
graph[pre_pkg_id] = deps
deps = []
if (pkg_id, dep_pkg_id) not in req_links:
req_links[(pkg_id, dep_pkg_id)] = []
req_links[(pkg_id, dep_pkg_id)].append(row[4])
if pkg_id == pre_pkg_id and dep_pkg_id == pre_dep_pkg_id:
continue
deps.append(dep_pkg_id)
pre_pkg_id = pkg_id
pre_dep_pkg_id = dep_pkg_id
if pre_pkg_id is not None:
graph[pre_pkg_id] = deps
scc = self.find_all_cycles(graph)
result = []
if scc is not None:
for comp in scc:
if len(comp) < 2:
continue
cycle_str = pkg_names[comp[0]]
for n0 in xrange(0, len(comp)):
n1 = n0 + 1 if n0 + 1 < len(comp) else 0
if (comp[n0], comp[n1]) in req_links:
pass
cycle_str += ' =(%s)=> %s' % (
';'.join(req_links[(comp[n0], comp[n1])]),
pkg_names[comp[n1]])
result.append(cycle_str)
return (result, len(result))
def main(args): def main(args):
options = parseargs() options = parseargs()
@ -610,6 +719,7 @@ def main(args):
symbols_not_resolved(dbc).print_text() symbols_not_resolved(dbc).print_text()
file_conflicts(dbc).print_text() file_conflicts(dbc).print_text()
provides_conflicts(dbc).print_text() provides_conflicts(dbc).print_text()
cycles(dbc).print_text()
conn.close() conn.close()
if __name__ == "__main__": if __name__ == "__main__":