229 lines
6.9 KiB
Python
229 lines
6.9 KiB
Python
#!/usr/bin/env python2
|
|
|
|
"""
|
|
module-dependencies.py -- PIKA 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 <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
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")
|