#!/usr/bin/env python2 """ module-dependencies.py -- PIKA library and core module dependency constructor Copyright (C) 2010 Martin Nordholts This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . This program uses graphviz (you need PyGraphViz) to construct a graph with dependencies between PIKA library and core modules. Run it from the source root. Note that you'll either need the very latest PyGraphViz binding or use this hack in agraph.py: --- agraph.py.orig 2010-01-04 16:07:46.000000000 +0100 +++ agraph.py 2010-01-04 16:13:54.000000000 +0100 @@ -1154,7 +1154,8 @@ class AGraph(object): raise IOError("".join(errors)) if len(errors)>0: - raise IOError("".join(errors)) + # Workaround exception throwing due to warning about cycles + pass return "".join(data) """ from __future__ import with_statement from sets import Set import os, re, pygraphviz # First make a sanity check if not os.path.exists("app") or not os.path.exists("libpika"): print("Must be run in source root!") exit(-1); # This file lives in libpika and is used by many low-level # libs. Exclude it in the calculations so the graph become nicer ignored_interface_files = [ "libpika/libpika-intl.h", ] # List of library modules libmodules = [ "libpika", "libpikabase", "libpikacolor", "libpikaconfig", "libpikamath", "libpikamodule", "libpikathumb", "libpikawidgets", ] # List of app modules # XXX: Maybe group some of these together to simplify graph? appmodules = [ "actions", "base", "composite", "config", "core", "dialogs", "display", "file", "gegl", "gui", "menus", "paint", "paint-funcs", "pdb", "plug-in", "tests", "text", "tools", "vectors", "widgets", "xcf", ] # Bootstrap modules, i.e. modules we assume exist even though we don't # have the code for them boostrap_modules = [ [ "GLib", ["glib.h"] ], [ "GTK+", ["gtk/gtk.h"] ], [ "GEGL", ["gegl.h"] ], [ "Pango", ["pango/pango.h"] ], [ "Cairo", ["cairo.h"] ], ] ## # Function to determine if a filename is for an interface file def is_interface_file(filename): return re.search("\.h$", filename) ## # Function to determine if a filename is for an implementation file, # i.e. a file that contains references to interface files def is_implementation_file(filename): return re.search("\.c$", filename) ## # Represents a software module. Think of it as a node in the # dependency graph class Module: def __init__(self, name, color, interface_files=[]): self.name = name self.color = color self.interface_files = Set(interface_files.__iter__()) self.interface_file_dependencies = Set() self.dependencies = Set() def __repr__(self): return self.name def get_color(self): return self.color def get_interface_files(self): return self.interface_files def get_interface_file_dependencies(self): return self.interface_file_dependencies def get_dependencies(self): return self.dependencies def add_module_dependency(self, module): if self != module: self.dependencies.add(module) # Represents a software module constructed from actual source code class CodeModule(Module): def __init__(self, path, color): Module.__init__(self, path, color) all_files = os.listdir(path) # Collect interfaces this module provides for interface_file in filter(is_interface_file, all_files): self.interface_files.add(os.path.join(path, interface_file)) # Collect dependencies to interfaces for filename in filter(is_implementation_file, all_files): with open(os.path.join(path, filename), 'r') as f: for line in f: m = re.search("#include +[\"<](.*)[\">]", line) if m: interface_file = m.group(1) # If the interface file appears to come from a core # module, prepend with "app/" m = re.search ("(.*)/.*", interface_file) if m: dirname = m.group(1) if appmodules.__contains__(dirname): interface_file = "app/" + interface_file self.interface_file_dependencies.add(interface_file) for ignored_interface_file in ignored_interface_files: self.interface_file_dependencies.discard(ignored_interface_file) # Initialize the modules to use for the dependency analysis modules = Set() for bootstrap_module in boostrap_modules: modules.add(Module(bootstrap_module[0], 'lightblue', bootstrap_module[1])) for module_path in libmodules: modules.add(CodeModule(module_path, 'coral1')) for module_path in appmodules: modules.add(CodeModule("app/" + module_path, 'lawngreen')) # Map the interface files in the modules to the module that hosts them interface_file_to_module = {} for module in modules: for interface_file in module.get_interface_files(): interface_file_to_module[interface_file] = module # Figure out dependencies between modules unknown_interface_files = Set() for module in modules: interface_files = filter (is_interface_file, module.get_interface_file_dependencies()) for interface_file in interface_files: if interface_file_to_module.has_key(interface_file): module.add_module_dependency(interface_file_to_module[interface_file]) else: unknown_interface_files.add(interface_file) if False: print "Unknown interface files:", unknown_interface_files # Construct a GraphViz graph from the modules dependency_graph = pygraphviz.AGraph(directed=True) for module in modules: dependency_graph.add_node(module, fillcolor=module.get_color(), style='filled') for module in modules: for depends_on in module.get_dependencies(): dependency_graph.add_edge(module, depends_on) # If module A depends on module B, and module B depends on module C, A # gets C implicitly. Perform a transitive reduction on the graph to # reflect this dependency_graph.tred() # Write result if True: dependency_graph.draw("devel-docs/pika-module-dependencies.svg", prog="dot") else: dependency_graph.write("devel-docs/pika-module-dependencies.dot")