自由気ままにアドベントカレンダー: ソースコードを効率良く読みたい(gtags編) #ieAdventCalendar2017

Share on:

Qiita Advent Calendar 2017とか技術系の記事持ち寄りカレンダー的なサムシングで賑わう昨今皆様いかがお過ごしですか。私はようやく右肩の痛みが収まりつつあります。続くかどうかも不明ですが一人気ままに書いてみます。統一テーマは「情報工学科(知能情報コース)にまつわる何某か」。テーマ付けてなくないと言われても仕方ないぐらいの条件ですが、付けたもの勝ちだし。適当にハッシュタグ(#ieAdventCalendar2017)も付けときます。後で学科のscrapboxにリンク貼る予定です。


前置きはこれぐらいにして1件目の本題「ソースコードを効率良く読みたい(gtags編)」。


1. これは何?

一言で述べるなら「ソースコード上に散らばってる情報を紐付けるツール」かな。例えば、、、

  • ある場所で宣言している変数Aがどこで使われているか?
  • 逆に、ある場所で使われている変数Bはどこで宣言されているか?

みたいなことを探す支援をしてくれます。上記は変数の例ですが、クラスや関数にも対応。対応言語は公式サイト: GNU GLOBAL source code tagging systemで調べよう。正式名称は「GLOBAL」ですが、歴史的な理由や設定ファイル名等で使われてる略称「gtags」と呼ばれることが多いかも。


2. 何ができるの?

ソースコードを読もうとすると、

  • ここで設定した変数は、どこでどう使われているのだろう?
  • ここで参照してる関数は、どこで定義されているのだろう?
  • このクラスはどういう継承になっているのだろう?

等、多数の階層関係にあるファイル群を行き来しながら解読していくことになるでしょう。IDEで定義元を辿ったりすることもできますが、gtagsを使うとターミナルやエディタで参照することができるようになります。


3. 環境構築@Mac編

動作確認したのは2017年12月22日時点。

    動作確認した環境
  • OS: macOS Sierra (10.12.6)
  • Python 3.6.1 :: Anaconda 4.4.0 (x86_64)
    環境構築@Mac編
  • ステップ1: globalのインストール。オプション無しだと素のglobalだけがインストールされますが、これだと標準対応してる言語(C言語とか)だけしか動かないので、Exuberant Ctagsと、Pygmentsを組み込んだ版をインストールするようにオプション指定しています。
    brew install global --with-ctags --with-pygments
    pip install pygments #不要かも?
    
  • ステップ2: emacsと連携するなら、~/.emacsに以下を設定しよう。setq以下はお好み。ショートカット設定です。vim連携したいなら、/usr/local/Cellar/global/6.6.1/share/gtags/gtags.vim参照するように設定すると良いんじゃないかな。多分。
    (setq load-path (cons "/usr/local/Cellar/global/6.6.1/share/gtags" load-path))
    (autoload 'gtags-mode "gtags" "" t)
    (setq gtags-mode-hook
          '(lambda ()
             (local-set-key "\M-t" 'gtags-find-tag)
             (local-set-key "\M-r" 'gtags-find-rtag)
             (local-set-key "\M-s" 'gtags-find-symbol)
             (local-set-key "\C-t" 'gtags-pop-stack)
             ))
    

4. 使ってみよう

コード例(Python): kelvinguu/neural-editor

これから上記の参考コードを読んでいくとしましょう。readmeにある実行例では、textmorph/edit_model/main.pyを動かす例が書かれているので、このファイルを眺めてみることにしましょう。

読みたいソースコードを用意したら、そのディレクトリに移動して「gtags –gtagslabel=pygments」を実行しましょう。コマンド実行したディレクトリ内にあるソースファイルを洗い出し、タグ情報を抽出&整理してくれます。前準備はこれだけ。以下では、作成したタグ情報を使ってみます。


[例1] main.pyの18行目で設定している変数 experiments はどう使われているのだろう?(コマンド利用編)

oct:tnal% global -x experiments
experiments 72 latex/namespacetextmorph_1_1edit__model_1_1main.tex \subsubsection{\texorpdfstring{experiments}{experiments}}
experiments 18 textmorph/edit_model/main.py experiments = EditTrainingRuns(check_commit=(args.check_commit==’strict’))
oct:tnal% global -xr experiments
experiments 23 textmorph/edit_model/main.py exp = experiments.new()
experiments 26 textmorph/edit_model/main.py exp = experiments[int(exp_id[0])]
experiments 32 textmorph/edit_model/main.py exp = experiments.new(config) # new experiment from config

太字はターミナル上で実行したコマンド。

1つ目のコマンド「global -x」で、指定したタグ(キーワードと読み替えてもらってok)がどこで定義されてるかの一覧を表示しています。どうやらtexファイルと、今参照しているmain.texの2箇所で定義されているようです。texファイルの方はPythonのコードとは無関係なので無視しましょうか。

2つ目のコマンド「global -xr」は、指定したタグがどこで参照されているかの一覧を表示しています。全てmain.pyで、先程の18行目で初期化された変数experimentsは、23行目で new() され、26行目で何やらリスト参照されているようです。また、32行目の new(config) があることから、news()する際に2通りの設定方法があるらしいことが読み取れます。


[例2] emacsでソース参照しながらタグ情報を利用してみる

emacsでtextmorph/edit_model/main.pyを開き、「Esc-x gtags-mode」と実行してgtagsモードに移りましょう。「Esc-x」は「Escキーを押して離して、xを入力」です。その後、「gtags」ぐらいまでを入力したらTABキーで残りのキーワードを補完入力してくれるはずです。「Esc-x gtags-mode」まで入力したら、エンターキーで入力確定します。すると、環境構築で指定したgtags.elを読み込んだ状態(gtagsモード)になります。

gtagsモードに移ったら、8行目の「set_log_level(‘DEBUG’)」がある行にカーソルを移動します。この状態で「Esc-x gtags」まで入力してTABキー押すと、関連コマンドの一覧が出てきます。キャンセルしたい場合には「Ctrl-G」(Controlキー押しながらGを押す)と、エディタ側にカーソルが戻ります。gtagsコマンドの詳細はマニュアル参照してもらうとして、ここでは「set_log_levelがどこで定義されているのかを探し、定義している箇所に移動する」ことをやってみます。

今、8行目の「set_log_level(‘DEBUG’)」がある行にカーソルがある状態です。ここで「Esc-x gtags-find-tags」と入力して実行してみて下さい。

Find tags: (default set_log_level)

という表示が出力されるはずです。特に指定しなければ、set_log_levelについてタグ情報を探すよという状態です。このままで良いので、リターンキーを押しましょう。そうすると、emacsの画面全体が新しくなり、「*GTAGS SELECT*」というバッファを開いた状態になり、以下のような情報が出力されているはずです。

set_log_level 150 gtd/log.py def set_log_level(level):
set_log_level 4 textmorph/edit_model/main.py from gtd.log import set_log_$
set_log_level 150 third-party/gtd/gtd/log.py def set_log_level(level):

上記は、
 ・set_log_levelというタグが3箇所で使われていること。
 ・1つ目は、gtd/log.pyというファイルで、150行目でdefしてる。
 ・2つ目は、今読んでるファイルと同じファイルで、4行目で import してる。
 ・3つ目は、third-party/gtd/gtd/log.pyというファイルで、150行目でdefしてる。
といったことを一覧表示しています。今はmain.pyを読んでいる最中で、上記2つ目のようにimportしてる情報を考えると、どうやらgtd.logモジュールで定義しているようだと想像できます。ということを踏まえ、1つ目の箇所に移動することにしましょう。

移動するには、emacs内で上下カーソルキー(もしくはCtrl-p or Ctrl-n)を使うことで、参照先を選択することができます。ここでは1つ目の「gtd/log.py」のある行にカーソルを移動させて、リターンキーを押しましょう。すると、gtd/log.pyの該当行(150行目)に移動してくれます。ここではdef文が書いてありますので、set_log_levelについて読み進めたい場合にはここを解読していくと良いでしょう。

補足として、環境設定でショートカットを設定しておいたならば、「Ctrl-t(gtags-pop-stack)」で「一つ前のバッファに戻る」ことができます。これは複数遡ることが可能です。あるファイルAから別ファイルBに飛び、そこからまた異なるファイルCに飛んだならば、Ctrl-tを1回実行するとファイルBに戻り、もう一度Ctrl-tすると最初のファイルAに戻れます。findで移動して、popで戻ることで行き来する形ですね。


まとめのようなもの

以上、gtagsを使ってソースファイルを読んでいく例を紹介してみました。今はIDEでも多くのことができるようになっていますが、ターミナル&エディタでもこのぐらいのことはできますし、他にもheml-gtagsなんてのもあるらしい。もしくは、Doxygenのようにソースコードからドキュメント生成してくれるツールもありますし、階層関係、継承関係、参照・被参照関係をGraphvizで可視化してくれたりと、支援してくれるツールはいろいろあります。実行しながら追いかけたいなら、PyCharmThonny(Python初心者向け)みたいな統合環境使うも良し、標準デバッガ pdfもあります。

ソースコード眺めてウンウン唸るだけではわからないので、ツールも必要に応じて使いこなしましょう! Happy code reading!