App: Remove duplicate from dependency graph #77

Merged
Jan Lindemann merged 2 commits from jan/fix/20260822-app-remove-duplicate-from-dependency-graph into master 2026-08-22 16:11:34 +02:00 AGit

View file

@ -232,18 +232,17 @@ class App(Base):
for project in projects: for project in projects:
if project in graph: if project in graph:
continue continue
for section in sections: deps = self.get_project_refs(
deps = self.get_project_refs( [project],
[project], ['pkg.requires.jw'],
['pkg.requires.jw'], sections,
sections, scope = Scope.One,
scope = Scope.One, add_self = False,
add_self = False, names_only = True,
names_only = True, )
) graph[project] = set(deps)
graph[project] = set(deps) for dep in deps:
for dep in deps: self.__read_dep_graph([dep], sections, graph)
self.__read_dep_graph([dep], sections, graph)
def __flip_dep_graph(self, graph: Graph) -> Graph: def __flip_dep_graph(self, graph: Graph) -> Graph:
ret: Graph = {} ret: Graph = {}
@ -259,45 +258,43 @@ class App(Base):
project: str, project: str,
graph: Graph, graph: Graph,
unvisited: list[str], unvisited: list[str],
temp: set[str], stack: list[str],
path: list[str], ) -> list[str] | None:
) -> str | None: if project in stack:
if project in temp:
log(DEBUG, 'found circular dependency at project', project) log(DEBUG, 'found circular dependency at project', project)
return project idx = stack.index(project)
return stack[idx:] + [project]
if project not in unvisited: if project not in unvisited:
return None return None
temp.add(project) stack.append(project)
if project in graph: if project in graph:
for dep in graph[project]: for dep in graph[project]:
last = self.__find_circular_deps_recursive( cycle = self.__find_circular_deps_recursive(
dep, graph, unvisited, temp, path dep, graph, unvisited, stack
) )
if last is not None: if cycle is not None:
path.insert(0, dep) return cycle
return last
unvisited.remove(project) unvisited.remove(project)
temp.remove(project) stack.pop()
return None return None
def __find_circular_deps(self, projects: list[str], def __find_circular_deps(self, projects: list[str],
flavours: list[str]) -> list[str]: flavours: list[str]) -> list[str]:
graph: Graph = {} graph: Graph = {}
ret: list[str] = []
self.__read_dep_graph(projects, flavours, graph) self.__read_dep_graph(projects, flavours, graph)
unvisited = list(graph.keys()) unvisited = list(graph.keys())
temp: set[str] = set()
flipped = self.__flip_dep_graph(graph) flipped = self.__flip_dep_graph(graph)
while unvisited: while unvisited:
project = unvisited[0] project = unvisited[0]
log(DEBUG, 'Checking circular dependency of', project) log(DEBUG, 'Checking circular dependency of', project)
last = self.__find_circular_deps_recursive( cycle = self.__find_circular_deps_recursive(project, flipped, unvisited, [])
project, flipped, unvisited, temp, ret if cycle is not None:
) # An edge a -> b in the flipped graph means that b
if last is not None: # depends on a, so reverse to report the cycle in the
log(DEBUG, f'Found circular dependency below {project}, last is {last}') # original direction
ret.append(last) cycle = list(reversed(cycle))
return ret log(DEBUG, f'Found circular dependency: {" -> ".join(cycle)}')
return cycle
return [] return []
def __init__(self, distro: Distro | None = None) -> None: def __init__(self, distro: Distro | None = None) -> None: