Skip to content
Snippets Groups Projects
Commit bef460b6 authored by Manuel Günther's avatar Manuel Günther
Browse files

Added script to create inter-package dependency graphs for bob packages.

parent 9fdbb5c5
No related branches found
No related tags found
No related merge requests found
from .new_version import main as new_version
from .dependency_graph import main as dependency_graph
# gets sphinx autodoc done right - don't remove it
__all__ = [_ for _ in dir() if not _.startswith('_')]
#!../bin/python
#!/usr/bin/env python
from __future__ import print_function
import subprocess
import pkg_resources
import tempfile, os
import argparse
def main(command_line_options = None):
"""
This script will generate a dependency graph of the given bob package(s) using the external tool ``dot``.
Packages can be either specified as a list of ``--packages`` or read from (several) ``--package-files``.
The latter option is mostly useful to generate a dot graph for all Bob packages.
The output is written to the given ``--output-file``, writing either the specified intermediate ``--dot-file``, or a temporary file.
When the ``--plot-external-dependencies`` is selected, also external (Python-)dependencies will be plotted as well, in red ellipses.
"""
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--packages", '-p', nargs = '+', default = [], help = "Which packages do you want to have dependencies for?")
parser.add_argument("--package-files", '-P', nargs = '+', default = [], help = "Read packages from the given files (usually requirement?.txt files of bob).")
parser.add_argument("--dot-file", "-W", help = "If specified, the .dot file is written to the given file.")
parser.add_argument("--output-file", "-w", default="dependencies.png", help = "Specify the (.png) file to write.")
parser.add_argument("--limit-packages", '-l', nargs = '+', default = ['bob', 'facereclib', 'antispoofing'], help = "Limit packages read from --package-files to the given namespaces")
parser.add_argument("--plot-external-dependencies", '-X', action='store_true', help = "Include external dependencies into the plot?")
parser.add_argument("--rank-base-tools-same", '-R', action = 'store_true', help = "Set the rank of packages bob.extension, bob.core and bob.blitz at the same size")
parser.add_argument("--vertical", '-V', action = 'store_true', help = "Display the dot graph in vertical direction")
parser.add_argument("--verbose", '-v', action = 'store_true', help = "Print more information")
args = parser.parse_args(command_line_options)
# collect packages
packages = args.packages[:]
for package_file in args.package_files:
for line in open(package_file):
splits = line.rstrip().split()
packages.extend([p for p in splits if p not in packages and p.startswith(tuple(args.limit_packages))])
# generate dependencies
dependencies = {}
has_parents = set()
# function to add dependencies of packages recursively
def _add_recursive(p):
# check if package already parsed
if p not in dependencies:
if args.verbose:
print("Checking %s" % p)
deps = pkg_resources.require(p)
dependencies[p] = [d.key for d in deps[1:]]
for d in dependencies[p]:
has_parents.add(d)
_add_recursive(d)
for package in packages:
_add_recursive(package)
# prune dependencies
pruned_dependencies = {}
for package in dependencies:
indirect_dependencies = set(d for i in [dependencies[dep] for dep in dependencies[package] if dep in dependencies] for d in i)
pruned_dependencies[package] = [dep for dep in dependencies[package] if dep not in indirect_dependencies]
# split all dependencies that are from bob (i.e., that belong to the --limit-packages) or not
bob = set(package for package in pruned_dependencies if package.startswith(tuple(args.limit_packages)))
non_bob = set(dep for package in bob for dep in pruned_dependencies[package] if not dep.startswith(tuple(args.limit_packages)))
final = set(package for package in bob if package not in has_parents)
# function to return a name for the package that can serve as a dot variable
def _n(p):
return p.replace(".", "_").replace("-","_")
# write dependency graph
dot_file = args.dot_file if args.dot_file is not None else tempfile.mkstemp(suffix='.dot')[1]
with open(dot_file, 'w') as f:
# open plot
f.write("digraph Bob {\n")
if not args.vertical:
f.write("\trankdir=LR;\n")
# write bob packages in squares
for package in bob:
f.write('\t%s [label="%s",shape=box,group=%s,style=filled,color=%s];\n' % (_n(package), package, "_".join(package.split('.')[:2]), 'green' if package in final else 'lightblue'))
# write non-bob packages in squares
if args.plot_external_dependencies:
for package in non_bob:
f.write('\t%s [label="%s",color=red];\n' % (_n(package), package))
# write dependencies
for package in bob:
for dep in pruned_dependencies[package]:
if dep in bob:
f.write('\t%s -> %s [color=blue];\n' % (_n(package), _n(dep)))
elif args.plot_external_dependencies:
f.write('\t%s -> %s [color=red,style=dashed];\n' % (_n(package), _n(dep)))
# rank all externals at the same level
if args.plot_external_dependencies:
f.write('\t{rank=same; %s }\n' % " ".join([_n(p) for p in non_bob]))
# rank base tools at the same level
if args.rank_base_tools_same:
f.write('\t{rank=same; bob_extension bob_core bob_blitz}\n')
f.write("}\n")
if args.verbose and args.dot_file is not None:
print("Wrote dot file %s" % dot_file)
# call dot
call = ["dot", '-y', '-o', args.output_file, '-Tpng:cairo:gd', dot_file]
if args.verbose:
call[1:1] = ['-v']
print("Calling dot: '%s'" % " ".join(call))
subprocess.call(call)
if args.verbose:
print("\nWrote file %s" % args.output_file)
# clean-up
if args.dot_file is None:
os.remove(dot_file)
......@@ -40,6 +40,7 @@ setup(
entry_points = {
'console_scripts': [
'bob_new_version.py = bob.extension.scripts:new_version',
'bob_dependecy_graph.py = bob.extension.scripts:dependency_graph',
],
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment