mirror of
https://abf.rosa.ru/djam/repo-analyzer.git
synced 2025-02-23 18:12:54 +00:00
Add cycles detection
This commit is contained in:
parent
6bdb49da9d
commit
183ad8fef6
1 changed files with 111 additions and 1 deletions
|
@ -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__":
|
||||||
|
|
Loading…
Add table
Reference in a new issue