Stormのソースコードを読むためのメモ

環境

GNU Global

GNU GLOBALはソースコードタギングツール。Emacs/Vimと組み合わせて使う。対応しているプログラミング言語はC, C++, Yacc, Java, PHP4 and assemblyだけだが、タグテーブル作成にctagsを利用するモードを使うとかなりの言語をカバーできる。
StormはプリミティブなパーツをJavaで定義し、それらをClojureでサーバデーモンとして組み上げ、Pythonスクリプトを利用して起動するという感じで言語を併用しているが、それらのソースコード間を横断的にタグジャンプしていくことができる。同じように、スクリプト言語Java、各種JVM言語を組み合わせて利用するプロダクトは多いので、応用しやすい気がする。
また、gtagsでは探したい文字列パターンを探して候補を全部表示してくれるので、Javaソースコードの関数定義を探すときにも、インタフェースだけではなく各実装の定義部分に直接飛べて便利だと感じる。
インストール方法は以下。

$ wget http://tamacom.com/global/global-6.2.9.tar.gz
$ tar zxf global-6.2.9.tar.gz
$ cd global-6.2.9
$ ./configure --prefix=/usr/local && make && sudo make install

インストールしたら、ホームディレクトリに設定ファイルをコピーし、修正する。Exuberant Ctagsを使う場合の設定のところで、Lispに対応するファイルの拡張子として.cljを追加する。

$ cp /usr/local/share/gtags/gtags.conf ~/.globalrc
$ vim ~/.globalrc
$ diff /usr/local/share/gtags/gtags.conf ~/.globalrc
77c77
<       :langmap=Lisp\:.cl.clisp.el.l.lisp.lsp:\
---
>       :langmap=Lisp\:.cl.clisp.el.l.lisp.lsp.clj:\

ソースツリーのトップでgtagsコマンドを実行し、タグテーブルを作成する。--gtagslabelオプションでctagsを使うモードを指定する。

$ git clone https://github.com/nathanmarz/storm.git
$ cd storm
$ gtags --gtagslabel=exuberant-ctags

すると、ソースコードのファイル名リストとタグテーブルが作成される。

$ ls G*
GPATH  GRTAGS  GTAGS
Emacs

Emacsからgtagsを利用するために、.emacsに以下のような内容を追加する。clojure-modeについては後述。

(setq load-path (cons "/usr/local/share/gtags" load-path))
(setq gtags-suggested-key-mapping t)
(setq gtags-path-style 'relative)
(when (locate-library "gtags") (require 'gtags))
(add-hook 'c-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'c++-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'java-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'perl-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'python-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'ruby-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'clojure-mode-hook '(lambda () (gtags-mode 1)))
(add-hook 'erlang-mode-hook '(lambda () (gtags-mode 1)))

タグテーブルを作成済みの状態で、gtags-modeをONにした各プログラミング言語用モードを開くと、自動的にタグテーブルを見つけてくれる。以下のようなキーバインディングを利用して、gtagsの機能を利用する。

M-.
タグテーブルからシンボル名の定義を検索
C-c g
ソースコードファイルから文字列をgrepで検索
M-*
ひとつ前のバッファに戻る。

カーソルを探したいシンボル名の上にもっていき、 M-. や C-c g を押すと、そこにあるシンボル名を検索することができる。(ミニバッファで手で入力することもできる)。キーを押すと、自動的に候補となるファイル名と該当行の一部が一覧表示されるので、そのなかからお目当てと思しき行を選んでリターンキーを押すと、そこを開くことができる。
特にClojureソースコードの場合、関数の定義場所をタグテーブルでみつけられないことが多いので、grepを多用することになる。gtags-modeの中からgrepを使うと、(GPATHを利用して)ソースコードだけを対象として検索することができるので、意外と速くて便利。
たぶんVimでも同じようなことができるはず。

clojure-mode

EmacsClojureモード。githubから取得する。

$ cd
$ git clone https://github.com/clojure-emacs/clojure-mode

clojure-mode用の設定として、.emacsに以下の設定を追加する。

(setq load-path (cons "~/clojure-mode" load-path))
(require 'clojure-mode)
(add-hook 'clojure-mode-hook
          '(lambda () (setq gtags-symbol-regexp "[A-Za-z_][A-Za-z_0-9\-\!\?]*")))

gtags-modeで現在のカーソル位置にあるシンボル名を検索しようとしても、Clojureの単語をハイフンでつないだ関数名がシンボルパターンとして認識されないので、パターンを修正をしている。
たぶんVimでも同じようなことができるはず。

予備知識

依存プロダクト

ストームが利用しているパーツであるところの、JZMQ、LMAX DisruptorやNetflix Curatorのツールがどんなものか知っておくとよい。これらのAPIを呼び出す部分から先はとりあえずブラックボックスとしておく。

JZMQ
ZeroMQのJavaバインディング
LMAX Disruptor
Worker内のスレッド間キュー
Netflix Curator
ZooKeeperのラッパー
Thrift

おなじみのRPCのフレームワーク。StormではおもにクライアントがNimbusにアクセスするためのAPIのために利用されている。
storm-core/src/storm.thriftにthrift固有の文法でデータ型とサービスが定義されていて、storm-core/src/genthrift.shをサーバ/クライアントのコードが生成される。生成されたコードはstorm-core/src/jvm/backtype/generatedにあるが、これは人間が読んでもあまり意味はない。

misc
  • ディレクトリ構造はcljとjvmの2つに分かれている。cljにはClojureの、jvmにはJavaのコードが置かれている。
  • パッケージ名のbacktypeはStorm開発者が所属していた会社の名前。Twitter社に買収された。
  • Clojureで分からないことがあったら、チートシートで調べるとよい。
  • clojureの関数の定義が見つけにくいことがある。
    • defnだけではなくdefnkとかdefserverfnみたいなマクロを作って利用していることもある。
    • letのところで関数を返す関数を呼び出してバインドしていることも多い。
  • letのbinding-form部分はパターンマッチングができるので、単純なシンボル名だけが書かれているとは限らない。
  • reifyはJavaの無名クラスと同じような感じで、インタフェースの実装しつつインスタンスを返す。
  • Clojureのコードはマクロが絡むとすこし難しいので、最初は無理に追わなくてもよいのでは。