ソースコードを快適に読むための GNU GLOBAL 入門 (前編)
ソースコードを読むときは、複数のファイルから目的の関数を探してエディタで開くという作業をすることが多い。 Eclipse や NetBeans などの統合開発環境上で Java を読む場合は、クラスやメソッドの一覧からソースコードへ簡単にジャンプすることができる。 ターミナル上で C を読む場合は、 grep コマンドで関数を探してエディタ (vim) で開いていたんだけど、ファイル数が多くなると目的の関数に辿り着くのが大変になってくる。 そこで GNU GLOBAL というソースコード解析ツールを試してみたら、ソースコードを読む作業がとても快適になった。
このツールの存在は前から知っていたんだけど、ネット上では「ソースコードタグシステム」、「ctagsのようなもの」という説明が多く、 ctags が何なのかを分かっていない僕には、便利だけど難しそうなものだと勝手に思い込んでいた。 でも、試してみたら簡単に使えて、すごく便利。もっと早く使っておけばよかった。 なので、 GNU GLOBAL を知らない人向けに、導入から使い方をまとめてみるよ。
GNU GLOBAL でできること
GNU GLOBAL のサイトから引用。
GNU GLOBAL は、ソースコードに索引付けを行うことで、大規模システムのハックやレビューを効率化するソフトウエアです。
ソースファイル中の指定したシンボルを高速に見つけ出し、素早くその場所に移動することができます。多くのサブディレクトリからなり、#ifdef や main() 関数を沢山含んでいるような、いわゆる巨大なプロジェクトをハックするのに役立ちます。ctags やetags に似た働きをしますが、エディタには依存せず様々な環境でご利用になれます。
GNU GLOBAL を使えば、ソースコードを読んでいて「この関数はどこで宣言されているんだろう」と思ったときに、その箇所をすぐに開くことができる。 Eclipse で F3 キーを押すと宣言部分へジャンプできるんだけど、同じようなことが vim や Emacs などのエディタでもできるようになる。
- 関数が宣言されている箇所を調べられる
- その関数が使われている場所も調べられる
- あるファイルに含まれる関数の一覧を見ることができる
対応している言語は C, C++, Yacc, Java, PHP4 。
GNU GLOBAL のメリット
GNU GLOBAL はエディタに依存せずに使えることが特徴。 vim と Emacs のどちらでも使うことができる。 統合開発環境を使わずにターミナル上でソースコードを読み書きする人にオススメ。
解析結果を HTML で出力することで、 Web ブラウザでソースを読むこともできる。 ふと思ったけど、突然知らないソースコードを渡されて、「これを明日までに読んでおいて」って言われた人にもオススメかもしれない。 その場でサッと HTML に変換して、 Web ブラウザで読んでると格好良く見えるかもね(半分冗談です)。
インストール方法
主要なディストリビューションならパッケージで簡単にインストールできるはず。 Mac でも MacPorts を使ってインストールできた。
$ port install global
root 権限が無い場合でも、ソースコードからコンパイルして、自分のホームディレクトリにインストールできる。 依存関係が無く、簡単にコンパイルできるのが嬉しい。
$ ./configure --prefix=$HOME $ make $ make install
bin ディレクトリには、主に以下のコマンドがインストールされる。
- gtags … 前準備で使う。ソースコードを解析して、解析結果をタグファイルと呼ばれるデータベースに格納する。
- global … タグファイルを使ってソースコードから関数の箇所を検索する。
- htags … ソースコードを HTML に変換して Web ブラウザで読めるようにする。
share/gtags ディレクトリには、他のエディタと連携するためのスクリプトがインストールされる。
使い方
Ruby1.9のソースコードで試してみる。 公式サイトからソースを取得して展開しておく。
$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p0.tar.gz $ tar zxvf ruby-1.9.1-p0.tar.gz $ cd ruby-1.9.1-p0
使い方 (1) - 準備
はじめに、 gtags コマンドを使ってソースコードを解析する。 これは(ソースが変更されない限りは)一回だけ実行すればいい。 先ほど展開した ruby-1.9.1 のソースディレクトリ上で gtags -v コマンドを実行する。 -vは進行状況を解析するためのオプションなので、指定しなくても OK 。
$ gtags -v [Sat Mar 07 18:38:17 JST 2009] Gtags started. Using default configuration. [Sat Mar 07 18:38:17 JST 2009] Creating 'GTAGS'. [1] extracting tags of array.c [2] extracting tags of bignum.c [3] extracting tags of blockinlining.c (中略) [341/343] extracting tags of win32/dir.h [342/343] extracting tags of win32/win32.c [343/343] extracting tags of win32/winmain.c [Sat Mar 07 18:38:22 JST 2009] Done.
手元の Mac では、5秒くらいで解析が終わった。 解析が終わると、 GPATH, GRTAGS, GSYMS, GTAGS という4つのファイルが生成される。 それぞれのファイルサイズは数MB。 中身は Berkeley DB のファイルみたい。
$ file G* GPATH: Berkeley DB 1.85/1.86 (Btree, version 3, little-endian) GRTAGS: Berkeley DB 1.85/1.86 (Btree, version 3, little-endian) GSYMS: Berkeley DB 1.85/1.86 (Btree, version 3, little-endian) GTAGS: Berkeley DB 1.85/1.86 (Btree, version 3, little-endian)
準備はこれだけ。 これでいつでもソースコードから好きな関数を調べられる。
使い方 (2) - コマンドラインから使う
いきなりエディタと連携する前に、まずはコマンドラインで GNU GLOBAL を使ってみて機能を把握した方が分かりやすい。 コマンドラインから関数を調べるには、 global コマンドを使う。
以下に使い方の例を書いていくけど、読むのが面倒な人はとりあえず最後の「global コマンドのまとめ」だけを読めば OK 。
ソースコード → 関数一覧
Ruby のソースコードにどんな関数が存在するかが分からないので、試しに string.c で定義されている関数の一覧を出力してみる。 「global -f ファイル名」を実行すれば、そのファイルに含まれる関数の一覧が表示される。
$ global -f string.c BEG 18 string.c #define BEG(no) regs->beg[no] END 19 string.c #define END(no) regs->end[no] rb_str_new_cstr 28 string.c #undef rb_str_new_cstr (略) rb_str_length 1000 string.c rb_str_length(VALUE str) rb_str_bytesize 1016 string.c rb_str_bytesize(VALUE str) rb_str_empty 1032 string.c rb_str_empty(VALUE str)
関数だけじゃなくて、 define されたシンボルも出力してくれている。 便利。
関数名 → ソースコード(定義)
逆に、「global 関数名」を実行すれば、関数名からその関数が定義されているソースコードを調べられる。 rb_str_length 関数が定義されている場所を調べてみる。
$ global rb_str_length string.c
-x オプションを付ければ、より詳細な情報(行数とその行の内容)も出力される。
$ global -x rb_str_length rb_str_length 1000 string.c rb_str_length(VALUE str)
関数名の指定では正規表現を使うこともできる。 「rb_str_」で始まる関数が定義されている箇所を調べてみる。 もちろん、 -x オプションとの併用もできる。
$ global '^rb_str_' bignum.c ext/iconv/iconv.c ext/openssl/ruby_missing.h include/ruby/intern.h object.c sprintf.c string.c transcode.c
関数名 → ソースコード(参照)
「global -r 関数名」を実行すれば、その関数が呼び出されている箇所を調べることができる。 rb_str_length 関数が参照されている箇所を調べてみる。
$ global -r rb_str_length ext/iconv/iconv.c include/ruby/intern.h re.c string.c
先ほどと同じように -x オプションを指定することで、行数とその行の内容を見ることができる。
$ global -rx rb_str_length rb_str_length 894 ext/iconv/iconv.c VALUE n = rb_str_length(StringValue(str)); rb_str_length 624 include/ruby/intern.h VALUE rb_str_length(VALUE); rb_str_length 2543 re.c VALUE l = rb_str_length(str); rb_str_length 6985 string.c return rb_str_length(rb_id2str(SYM2ID(sym))); rb_str_length 7085 string.c rb_define_method(rb_cString, "length", rb_str_length, 0); rb_str_length 7086 string.c rb_define_method(rb_cString, "size", rb_str_length, 0);
関数名の一部 → 関数名
-c オプションを使うと、関数名の一部から関数名を調べられる。 「rb_str_l」で始まる関数の一覧を調べてみる。
$ global -c rb_str_l rb_str_length rb_str_ljust rb_str_lstrip rb_str_lstrip_bang
先ほどの正規表現を使った「global '^rb_str'」は「関数名の一部 → ファイル名」だったけど、 -c オプションの場合は「関数名の一部 → 関数名」になっているところが異なる。
このオプションは単独で使うよりも、シェルの補完として使った方が便利。 例えば、 zsh の場合は
$ global rb_str_e
と入力した状態で Tab キーを押すと以下のように補完候補が表示される。
bash の設定方法もマニュアル - 2.3 Applied usageに書かれている。
$ funcs() { local cur cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=(`global -c $cur`) } $ complete -F funcs global
ソースコードを grep する
関数名だけじゃなくて、ソースコード全体を grep することもできる。 試しに openssl という文字が含まれているソースを検索してみる。
global -g openssl ext/digest/md5/md5ossl.h ext/digest/rmd160/rmd160ossl.h ext/digest/sha1/sha1ossl.h ext/openssl/openssl_missing.c ext/openssl/openssl_missing.h ext/openssl/ossl.c ext/openssl/ossl.h ext/openssl/ossl_digest.c ext/openssl/ossl_engine.c ext/openssl/ossl_pkey_ec.c ext/openssl/ruby_missing.h
他のコマンドと同じように、 -x オプションを付ければ詳細情報が出力される。
$ global -gx openssl openssl 7 ext/digest/md5/md5ossl.h #include <openssl/md5.h> openssl 7 ext/digest/rmd160/rmd160ossl.h #include <openssl/ripemd.h> openssl 7 ext/digest/sha1/sha1ossl.h #include <openssl/sha.h> (以下略)
global コマンドのまとめ
global コマンドのうち、ここで紹介したよく使いそうなものをまとめておく。 これだけ覚えておけば、とりあえず役に立つんじゃないかな。
global 関数名 | 関数名 → ソースコード (定義) |
global -r 関数名 | 関数名 → ソースコード (参照) |
global -f ファイル名 | ソースコード → 関数一覧 |
global -c 関数名の一部 | 関数名の一部 → 関数一覧 |
global -g 検索文字列 | ソースコードの grep |
また、これらのコマンドに -x オプションを付ければ、詳細情報 (行番号と内容) が出力される。
エディタとの連携
global コマンドを使うだけでも、 grep 検索に頼っていた時よりもすごく便利になった。 でも、 global が本領を発揮するのは、 vim や Emacs などのエディタと連携させた時。 長くなってきたので、続きは次の日記に書くよ。