debパッケージの依存関係を図示
ubuntuがGにバージョンアップしたので、どかっとアップデートした。ついでにインストールしてあるパッケージの依存関係を把握したくなってdpkg-ruby経由でdotファイル作ってgraphvizで図示してみた。
この画像は真ん中へんを抽出してPNGにしたもの。オリジナルはSVGファイルにしたのでズームできます。こちら(http://sshi.s57.xrea.com/archive/deb_graph_spline.xml)。あたらしーバージョンのoperaかfirefoxならそのまま見れると思います。ローカルに落として見る場合は拡張子をsvgにしないといけないかも。
graphvizでもレイアウトエンジンがdotだとひどいグラフがでてくるが、fdpにしたら計算時間の増加とひきかえに見れるグラフがでてきた。neatoやfdpは無向グラフじゃないと駄目かと思ってたけど有向グラフでもいけるんだな。あと、依存数の大きいパッケージはでかくなるようにdotファイルに手をくわえてある。
いまは500弱しかパッケージがはいってない(普通はもっとはいってるだろう)けど、これ以上数が増えるとgraphvizの限界を越えるかなあ。まだ大丈夫だろうか。
全体像の図示としてはわりと満足だけど、より詳しくパッケージ間の関係をみようとするとこれではまだわかりにくい。選択したパッケージの依存先や依存元をハイライトする、とかインタラクティブな仕組みがほしいなあ。何でどう作るのがいちばん楽だろうか?
画像の作りかた (2007/10/28 追記)
画像を作るのに使ったRubyスクリプトをはりつけておきます。
ほとんどの仕事はgraphvizがやってるので、graphviz必須。あと、rubyからdpkg経由してパッケージ情報を参照するライブラリ、dpkg-rubyも必須です。aptでいれましょう。
以下のRubyスクリプトを走らせると、パッケージ間の依存関係を保持したdotファイルができるのでそれをgraphvizにくわせてあとは好きに。例えば、make_deb_graph.rbという名前で保存して、
とか。ちなみに、Athlon64 3000+なwindows XP上のVMWare上のubuntuで500弱のパッケージのある環境で走らせたら20分くらいかかりました。
ソースみればわかると思うけど、抽出してるのはDependsとPre-Dependsの情報だけ。なので、仮想パッケージを介した関係は抽出できてません。Providesを逆向きに追加すればそれでいいのかな?
以下ソース。
require 'debian' class Debian::Dep def e @deps end end module DebGraph module_function def make_dep_hash dep_hash = {} Debian::Dpkg.status.each_package {||deb| next unless deb.status == "installed" dep_hash[deb.package] = (collect_packages(deb,"depends") + collect_packages(deb,"pre-depends")).uniq } dep_hash end def collect_packages(deb,rel) (deb.deps(rel).map { |d| d.e.map {|f| f.package}}).flatten end def make_rev_dep_hash(dep_hash) rev_dep_hash = Hash.new {|hash,key| hash[key]=[]} dep_hash.each do |deb_name,ar| ar.each do |dep_deb_name| rev_dep_hash[dep_deb_name] << deb_name end end rev_dep_hash end def make_dot_file(dep_hash,scale_map) id_map = make_id_map(dep_hash) ret = [] ret << "digraph G {" ret << 'graph [size="7,10" page="8.5,11" splines=True];' ret << "edge[len=5];" all_list = dep_hash.keys all_list.each {|deb_name| point = scale_map[deb_name] || 0 point = (Math.log(point+2)) * 20 ret << "#{id_map[deb_name]} [label=\"#{deb_name}\"" + "fontsize=\"#{point}\"];" } dep_hash.each do |parent,c_ar| c_ar.each do |c| next unless all_list.include?(c) ret << "#{id_map[parent]} -> #{id_map[c]}" end end ret << "}" ret.join("\n") end def make_id_map(dep_hash) id_map = {} all_names = (dep_hash.keys + dep_hash.values.flatten).uniq all_names.each_with_index do |n,i| id_map[n] = i end id_map end def main dep_hash = make_dep_hash rev_dep_hash = make_rev_dep_hash(dep_hash) scale_map = {} rev_dep_hash.each do |name,rev| scale_map[name] = rev.size end puts make_dot_file(dep_hash,scale_map) end end DebGraph.main if __FILE__ == $0