Skip to content

Instantly share code, notes, and snippets.

@dirk-thomas
Last active December 1, 2016 17:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dirk-thomas/105892899af14ac64697 to your computer and use it in GitHub Desktop.
Save dirk-thomas/105892899af14ac64697 to your computer and use it in GitHub Desktop.
visualize package dependencies
#!/usr/bin/env python3
import os
import sys
from ament_tools.packages import find_unique_packages
import pygraphviz
def main(group_by_path=True, tred=False):
path = '.'
if len(sys.argv) == 2:
path = sys.argv[1]
pkgs = find_unique_packages(path)
pkgs_by_name = dict([(pkg.name, pkg) for pkg_path, pkg in pkgs.items()])
pkg_names = pkgs_by_name.keys()
graph = pygraphviz.AGraph(directed=True, strict=False) # , concentrate=True)
# add all packages as nodes
for pkg_name in sorted(pkg_names):
graph.add_node(pkg_name)
# group packages by parent folder
groups = {}
for pkg_path, pkg in pkgs.items():
parent_path = os.path.dirname(pkg_path)
if parent_path not in groups:
groups[parent_path] = []
groups[parent_path].append(pkg.name)
# collect all dependencies by type
# and add edges for them
build_depends = {}
run_depends = {}
test_depends = {}
for pkg_name in sorted(pkg_names):
pkg = pkgs_by_name[pkg_name]
build_depends[pkg_name] = set([])
for dep in pkg.build_depends + pkg.buildtool_depends:
if dep.name in pkg_names and dep.name not in build_depends[pkg_name]:
build_depends[pkg_name].add(dep.name)
run_depends[pkg_name] = set([])
for dep in pkg.build_export_depends + pkg.buildtool_export_depends + pkg.exec_depends:
if dep.name in pkg_names and dep.name not in run_depends[pkg_name]:
run_depends[pkg_name].add(dep.name)
test_depends[pkg_name] = set([])
for dep in pkg.test_depends:
if dep.name in pkg_names and dep.name not in test_depends[pkg_name]:
test_depends[pkg_name].add(dep.name)
# add edges using parallel splines instead of separately routed multiedges
for pkg_name in sorted(set(build_depends.keys()) | set(run_depends.keys()) | set(test_depends.keys())):
build_deps = set(build_depends.get(pkg_name, []))
run_deps = set(run_depends.get(pkg_name, []))
test_deps = set(test_depends.get(pkg_name, []))
for dep in sorted(build_deps | run_deps | test_deps):
colors = []
if dep in build_deps:
colors.append('red')
if dep in run_deps:
colors.append('darkgreen')
if dep in test_deps:
colors.append('blue3')
graph.add_edge(pkg_name, dep, color=':'.join(colors))
print(graph.to_string())
# add subgraphs based on rank
if not group_by_path:
depends = {}
for pkg in pkgs.values():
depends[pkg.name] = build_depends[pkg.name] | run_depends[pkg.name] | test_depends[pkg.name]
rank = 1
while depends:
leafs = [pkg_name for pkg_name, deps in depends.items() if not deps]
for leaf in leafs:
del depends[leaf]
for deps in depends.values():
deps -= set(leafs)
graph.add_subgraph(sorted(leafs), name='r%d' % rank, rank='same')
rank += 1
# add subgraphs based on grouping
if group_by_path:
attributes = {
'color': 'gray',
'fontcolor': 'gray',
'style': 'bold',
'weight': 0,
}
i = 0
for parent_path, pkg_names in groups.items():
i += 1
attributes.update({
'label': parent_path,
})
# prevent edges to be duplicated inside subgraph
edges_func = graph.edges
def empty(*args, **kwargs):
return []
graph.edges = empty
subgraph = graph.add_subgraph(
nbunch=pkg_names, name='cluster%d' % i, **attributes)
graph.edges = edges_func
if tred:
all_edges = graph.edges()
# remove transitive dependencies
graph.tred()
graph.layout('dot')
# if tred:
# # readd transitive dependencies after layout
# for edge in all_edges:
# if not graph.has_edge(edge):
# graph.add_edge(edge)
graph.draw('ament_graph.png')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment