pythonとgraphvizでビジュアルmakedepのようなものを作ってみた
仕事で、C/C++のソースコードのリファクタリング(ほとんどリバースだけど)をする必要が出てきました。コードを見ると、無節操にヘッダーファイルに別のヘッダーファイルをインクルードしているため、コードの階層分けすら困難を極めそうな状況です。
で、まずはヘッダーファイルの依存関係をグラフ化して見えるようにしたいなと思い、pythonでちょこちょことツールを
作ってみました。ソースコードから '#include "xxxxx"'を reパターンマッチで拾い出して、ファイルのインクルード関係をディクショナリに記録し、最後にgraphvizでグラフ表示するという簡単なツールです。
同じような苦労をしている方の何かの参考になれば幸いです。
#!/usr/local/bin/python # coding: utf8 ################################################################################ # incdep.py # Description: C言語のインクルードファイルの依存グラフを生成 # # Author: shozo fukuda # Date: Fri Jan 06 09:52:05 2012 # Last revised: $Date$ # Application: Python 2.7.2 ################################################################################ #<IMPORT> import os,sys,string import re import pydot import getopt #<SUBROUTINE>################################################################### # Function: 使用例の表示 # Description: # Dependencies: ################################################################################ def usage(): program = os.path.basename(sys.argv[0]) print u""" C言語のインクルードファイルの依存関係グラフ作成 Usage: %s [-I<dir>] <ソースファイル名> -I<dir> ディレクトリ<dir>の中も検索します """ %(program) #<SUBROUTINE>################################################################### # Function: ファイル検索 # Description: 名前が<name>のファイルをカレント・ディレクトリまたは検索ディレク # トリ・リスト<dirs>で検索する。ファイルが見つかった場合は、ファイ # ルのそのパス名を返す。ファイルが見つからない場合はNoneを返す。 # Dependencies: ################################################################################ def findfile(name, dirs, curdir='.'): """ ファイル検索 名前が<name>のファイルをカレント・ディレクトリ<curdir>、検索ディレクトリ・リス ト<dirs>の順に検索する。ファイルが見つかった場合は、その パス名を返す。 ファイルが見つからない場合はNoneを返す。 """ path = os.path.join(curdir, name) if os.path.isfile(path): return path for dir in dirs: path = os.path.join(dir, name) if os.path.isfile(path): return path return None #<CLASS>######################################################################## # Class: インクルードファイルの依存関係辞書 # Description: # Dependencies: ################################################################################ class Mkdep(dict): re_include = re.compile(r'#\s*include\s+\"(.+)\"') tr_sep = string.maketrans('\\', '/') def __init__(self, name): name = Mkdep.abspath(name) self.list = [name] # 対象ファイル・リスト self.dirs = [] # 検索ディレクトリ・リスト self.home = os.getcwd() @staticmethod def abspath(name): """ ファイル<name>の絶対パスを返す。パスの区切り文字は'/'に置き換える。""" return Mkdep.normpath(os.path.abspath(name)) @staticmethod def relpath(name, curdir): """ ファイル<name>のディレクトリ<curdir>からの相対パスを返す。パスの区切り文字 は'/'に置き換える。 """ return Mkdep.normpath(os.path.relpath(name, curdir)) @staticmethod def normpath(name): """ ファイル<name>の文字列を正規化する。 """ return os.path.normpath(os.path.normcase(name)).translate(Mkdep.tr_sep) def add_dir(self, dir): """ ファイルを検索するディレクトリを登録する。 """ if dir not in self.dirs: self.dirs.append(dir) def gather(self): """ #include宣言からファイルの依存関係を調べて辞書をつくる。 """ for father in self.list: cwd = os.path.dirname(father) self[father] = [] f = open(father) for text in f: match = Mkdep.re_include.match(text) if match: child = findfile(match.group(1), self.dirs, cwd) if child: child = Mkdep.normpath(child) self[father].append(child) if child not in self.list: self.list.append(child) else: child = Mkdep.normpath(match.group(1)) self[father].append(child) f.close() def write_gif(self, name): """ 依存関係グラフをGIFフォーマットで作成・保存する。 """ graph = pydot.Dot() graph.set_graph_defaults(rankdir='LR') graph.set_node_defaults(shape='box', style='rounded', fontname="Arial") for (father, children) in self.iteritems(): father = Mkdep.relpath(father, self.home) for child in children: child = Mkdep.relpath(child, self.home) edge = pydot.Edge(father, child) graph.add_edge(edge) graph.write_gif(name) def dump(self): """ 依存関係辞書を印字する。 """ for (father, children) in self.iteritems(): for child in children: print "%s,%s" %(father, child) #<MAIN>######################################################################### # Function: メインルーチン # Description: 簡易コマンドラインI/F # Dependencies: ################################################################################ if __name__ == '__main__': try: f = open('incdep.cfg') cfgs = f.read().split() f.close() except IOError: cfgs = [] try: opts, args = getopt.getopt(cfgs + sys.argv[1:], "I:") except getopt.error, v: print "Error:", v usage() exit(1) if not args: print "Error: expect file name" usage() exit(1) src = args[0] dst = os.path.splitext(src)[0] + '.gif' mkdep = Mkdep(src) for opt, val in opts: if opt == '-I': mkdep.add_dir(val) mkdep.gather() mkdep.write_gif(dst) # incdep.py