[gimp] tools: Add tool to construct a dependency graph between modules



commit c116363731e6b9eb1ba42adec6c4fe4e4b4d608f
Author: Martin Nordholts <martinn src gnome org>
Date:   Mon Jan 4 16:58:52 2010 +0100

    tools: Add tool to construct a dependency graph between modules
    
    Add Python tool to construct a dependency graph between GIMP library
    and core modules using graphviz and PyGraphViz.

 tools/module-dependencies.py |  228 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 228 insertions(+), 0 deletions(-)
---
diff --git a/tools/module-dependencies.py b/tools/module-dependencies.py
new file mode 100755
index 0000000..54f5975
--- /dev/null
+++ b/tools/module-dependencies.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+
+"""
+module-dependencies.py -- GIMP library and core module dependency constructor
+Copyright (C) 2010  Martin Nordholts <martinn src gnome org>
+
+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 <http://www.gnu.org/licenses/>.
+
+
+
+This program uses graphviz (you need PyGraphViz) to construct a graph
+with dependencies between GIMP 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("libgimp"):
+    print("Must be run in source root!")
+    exit(-1);
+
+
+# This file lives in libgimp and is used by many low-level
+# libs. Exclude it in the calculations so the graph become nicer
+ignored_interface_files = [
+    "libgimp/libgimp-intl.h",
+    ]
+
+# List of library modules
+libmodules = [
+    "libgimp",
+    "libgimpbase",
+    "libgimpcolor",
+    "libgimpconfig",
+    "libgimpmath",
+    "libgimpmodule",
+    "libgimpthumb",
+    "libgimpwidgets",
+    ]
+
+# 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/gimp-module-dependencies.svg", prog="dot")
+else:
+    dependency_graph.write("devel-docs/gimp-module-dependencies.dot")



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]