<file_sep>=======================
 Oslo Dependency Graph
=======================

This repository contains some tools for working out the dependencies
between various libraries, being managed under the OpenStack Oslo
program.

The status.txt document comes from (and should be synced with)
https://wiki.openstack.org/wiki/Oslo/GraduationStatus

The graphing tools rely on having the graphviz toolkit from
http://www.graphviz.org/Documentation.php installed.

Run ``./make_image.sh`` and revel in the pretty PNGs.
<file_sep>#!/usr/bin/env python
"""Produce a dependency graph in the DOT language.
"""
import collections
import itertools
import fileinput

def get_deps_by_module(infile):
    library_name = None
    dependencies = {}
    libraries = {}
    for n, line in enumerate(fileinput.input(infile)):
        if line.startswith('== '):
            # New lib
            library_name = line.strip().strip('=').strip()
            libraries[library_name] = []
            module_name = None
        elif line.startswith('=== '):
            # New module
            module_name = line.strip().strip('=').strip()
            libraries[library_name].append(module_name)
            dependencies[module_name] = []
        elif line.startswith(':Depends on:'):
            # Dependencies
            if not module_name:
                raise ValueError('found dependencies on line %d without a module' % n)
            depstr = line.rpartition(':')[-1].strip()
            if depstr != '(none)':
                deps = [d.strip() for d in depstr.split(',')]
                dependencies[module_name] = deps
    return libraries, dependencies

    
_node_namer = iter('N%d' % i for i in itertools.count())
_node_names = collections.defaultdict(_node_namer.next)
_known_nodes = set()
def generate_node(label, shape='oval'):
    if label not in _known_nodes:
        print '  %s [ label = "%s", shape=%s ];' % (_node_names[label], label, shape)
        _known_nodes.add(label)

    
def print_graph(libraries, dependencies):

    # Invert the library contents so we can link
    # to the library instead of its contents
    mods_to_lib = {}
    for name, modules in sorted(libraries.items()):
        for mod in modules:
            mods_to_lib[mod] = name

    print 'digraph oslo {'

    # Represent the module dependencies as library dependencies
    # with the module name as the edge label
    edges = set()
    for mod, deps in sorted(dependencies.items()):
        src_lib_name = mods_to_lib.get(mod, mod)
        generate_node(src_lib_name)
        src_lib = _node_names[src_lib_name]
        for d in deps:
            dest_lib_name = mods_to_lib.get(d, d)
            if dest_lib_name == src_lib_name:
                continue
            generate_node(dest_lib_name)
            dest_lib = _node_names[dest_lib_name]
            edge = '%s -> %s [ label = "%s:%s" ];' % (src_lib, dest_lib, mod, d)
            #edge = '%s -> %s;' % (src_lib, dest_lib)
            if edge not in edges:
                generate_node(src_lib_name)
                generate_node(dest_lib_name)
                print edge
                edges.add(edge)

    print '}'


libraries, dependencies = get_deps_by_module('status.txt')
print_graph(libraries, dependencies)
<file_sep>#!/bin/sh

set -ex

format=$1
if [ -z "$format" ]
then
    format="png"
fi

function draw {
    typeset base=$1
    dot -T${format} -o${base}.${format} ${base}.dot
#    neato -T${format} -o${base}_neato.${format} ${base}.dot
#    twopi -T${format} -o${base}_twopi.${format} ${base}.dot
    circo -T${format} -o${base}_circo.${format} ${base}.dot
}

depend_graph.py > modules.dot
draw modules

lib_graph.py > libs.dot
draw libs

lib_graph_verbose.py > libs_verbose.dot
draw libs_verbose

lib_graph_uncluttered.py > libs_uncluttered.dot
draw libs_uncluttered
<file_sep>#!/usr/bin/env python
"""Produce a dependency graph in the DOT language.
"""
import collections
import itertools
import fileinput


def get_deps_by_module(infile):
    library_name = None
    dependencies = {}
    libraries = {}
    for n, line in enumerate(fileinput.input(infile)):
        if line.startswith('== '):
            # New lib
            library_name = line.strip().strip('=').strip()
            libraries[library_name] = []
            module_name = None
        elif line.startswith('=== '):
            # New module
            module_name = line.strip().strip('=').strip()
            libraries[library_name].append(module_name)
            dependencies[module_name] = []
        elif line.startswith(':Depends on:'):
            # Dependencies
            if not module_name:
                raise ValueError('found dependencies on line %d without a module' % n)
            depstr = line.rpartition(':')[-1].strip()
            if depstr != '(none)':
                deps = [d.strip() for d in depstr.split(',')]
                dependencies[module_name] = deps
    return libraries, dependencies

    
_node_namer = iter('N%d' % i for i in itertools.count())
_node_names = collections.defaultdict(_node_namer.next)
_known_nodes = set()
def generate_node(label):
    if label not in _known_nodes:
        print '  %s [ label = "%s" ];' % (_node_names[label], label)
        _known_nodes.add(label)

    
def print_graph(libraries, dependencies):

    print 'digraph oslo {'

    # Each library is a subgraph
    for name, modules in sorted(libraries.items()):
        n = _node_namer.next()
        print 'subgraph %s {' % n
        print '  label = "%s";' % name
        print '  style = filled;'
        print '  color=black;'
        print '  node [style=filled,color=lightgrey];'
        for mod in modules:
            generate_node(mod)
        print '}'

    # Generate node label statements
    for mod, deps in sorted(dependencies.items()):
        generate_node(mod)
        for d in deps:
            generate_node(d)
                
    print

    # Generate the edges
    for mod, deps in sorted(dependencies.items()):
        for d in deps:
            print '  %s -> %s;' % (_node_names[mod], _node_names[d])

    print '}'


libraries, dependencies = get_deps_by_module('status.txt')
print_graph(libraries, dependencies)
<file_sep>#!/usr/bin/env python
"""Produce a dependency graph in the DOT language.
"""
import collections
import itertools
import fileinput

def get_deps_by_module(infile):
    library_name = None
    module_name = None
    dependencies = {}
    libraries = {}
    for n, line in enumerate(fileinput.input(infile)):
        if line.startswith('== '):
            # New lib
            library_name = line.strip().strip('=').strip()
            libraries[library_name] = {
                'status': 'dev',
                'modules': [],
            }
            module_name = None
            if library_name.startswith('INCUBATOR:'):
                libraries[library_name]['status'] = 'Incubating'
        elif line.startswith('=== '):
            # New module
            module_name = line.strip().strip('=').strip()
            libraries[library_name]['modules'].append(module_name)
            dependencies[module_name] = []
        elif not module_name and line.startswith(':S:'):
            # library status
            libraries[library_name]['status'] = line.rsplit(':', 1)[-1].strip() or 'dev'
        elif line.startswith(':Depends on:'):
            # Dependencies
            if not module_name:
                raise ValueError('found dependencies on line %d without a module' % n)
            depstr = line.rpartition(':')[-1].strip()
            if depstr != '(none)':
                deps = [d.strip() for d in depstr.split(',')]
                dependencies[module_name] = deps
    return libraries, dependencies


_node_colors = {
    'dev': ('lightgrey', 'black'),
    'Released': ('steelblue', 'white'),
    'Graduating': ('orange', 'black'),
    'Deleting': ('crimson', 'white'),
    'Next': ('yellow', 'black'),
    'Incubating': ('lightgrey', 'red'),
}

_node_namer = iter('N%d' % i for i in itertools.count())
_node_names = collections.defaultdict(_node_namer.next)
_known_nodes = set()
def generate_node(label, shape='oval', bgcolor='white', fgcolor='black'):
    if label not in _known_nodes:
        print '  %s [ label = "%s", shape=%s, color=%s, fontcolor=%s style=filled ];' % \
            (_node_names[label], label, shape, bgcolor, fgcolor)
        _known_nodes.add(label)


def print_graph(libraries, dependencies):

    # Invert the library contents so we can link
    # to the library instead of its contents
    mods_to_lib = {}
    for name, attrs in sorted(libraries.items()):
        for mod in attrs['modules']:
            mods_to_lib[mod] = name

    print 'digraph oslo {'

    # Show all of the libraries, even if there are no dependencies
    for name, attrs in sorted(libraries.items()):
        bg, fg = _node_colors.get(attrs['status'], ('white', 'black'))
        generate_node(name, bgcolor=bg, fgcolor=fg)

    # Represent the module dependencies as library dependencies
    # with the module name as the edge label
    edges = set()
    for mod, deps in sorted(dependencies.items()):
        src_lib_name = mods_to_lib.get(mod, mod)
        #generate_node(src_lib_name)
        src_lib = _node_names[src_lib_name]
        for d in deps:
            dest_lib_name = mods_to_lib.get(d, d)
            if dest_lib_name == src_lib_name:
                continue
            #generate_node(dest_lib_name)
            dest_lib = _node_names[dest_lib_name]
            #edge = '%s -> %s [ label = "%s" ];' % (src_lib, dest_lib, d)
            edge = '%s -> %s;' % (src_lib, dest_lib)
            if edge not in edges:
                generate_node(src_lib_name)
                generate_node(dest_lib_name)
                print edge
                edges.add(edge)

    print '}'


libraries, dependencies = get_deps_by_module('status.txt')
print_graph(libraries, dependencies)
