OMake つかったらC言語でプログラム書く手間がバカみたいに減った

OMakeすごい。OMakeはマジですごい。

OMakeはGNU makeの代替品みたいなものなんだけど、正直なところこのツールの強力さはGNU makeと比べると失礼なくらいすごい。これのおかげで、「コード修正→ビルド→デバッグ→コード修正→・・・」のループの、ビルドにあたる作業がほぼ消え去った。

ファイルの依存関係の解析がとにかくすごい。よくあるユースケースなんかの場合、最小限の手間でほぼ完璧に依存関係を網羅して、よしなにビルドしてくれる。

とりあえず、はやみずが実際に使ってみたケースを例にとってそのすごさの一端を紹介しようと思う。

case study

論より証拠ということで、自分が OMake を試しにつかってみたケースを紹介する。C言語でスタティックライブラリを作っていて、それに加えて簡単なテストプログラムを書いている。

  • /include/ 以下にヘッダファイルが全部ある
  • /src/ 以下に *.c があり、それをまとめて libfoo.a にする
  • /test/testfoo.c がテストプログラム。これと /src/libfoo.a をリンクして実行ファイルを作る

これらをOMakeでビルドするための手順を書いていく

まずはおまじない

プロジェクトのルートとなるディレクトリで、

$ omake --install

を実行する。すると、 OMakefile と OMakeroot という2つのファイルができる。OMakerootは編集する必要はないので、OMakefile を書き換える。

書き換えるのは2点。

# Delete this line once you have configured this file
eprintln($(CWD)/OMakefile is not configured)

という2行を削除。eprintln によってビルドを実行した際にエラーメッセージが表示されるので、うざいから消すだけである。

そして、

# .SUBDIRS:

の行のコメントを外して、ビルドを実行したいディレクトリ名を追加する。実際にビルドが行われるのは、 /src と /test なので、

.SUBDIRS: src test

とすればよい。これで、src と test の中にそれぞれ入ってビルドを実行するよう指示したことになる。

OMake では、プロジェクトのルートをOMakerootによって知ることができる。それにより、サブディレクトリの中からでも、プロジェクト全体に渡るようなビルドを実行することができる。

/src/libfoo.a を作る

つぎにライブラリ libfoo.a をビルドするルールを記述しよう。/src/OMakefile というファイルを作り、次のように記述する。libfoo.a は、foo_a.c、 foo_b.c、 foo_c.c という3つのファイルをくっつけて作りたいとする。

CFLAGS += -g -I../include

.DEFAULT: $(StaticCLibrary libfoo, foo_a foo_b foo_c)

これでおしまい。OMake には StaticCLibrary(スタティックライブラリをビルド) や CProgram(C言語の実行ファイルをビルド)など、典型的なユースケースのための関数群が予め用意されている。もちろん、そういう関数を自分で作ることもできる。OMakeすごい!

ところで、foo_a.c などが include しているファイル (たとえば /include/foo.h をインクルードしていたとしよう)などは指定しなくてもいいのだろうか、という疑問が湧いてくると思う。

実はもう指定されている。foo_a.c のファイルの中に、 "#include " と。

OMake では、 gcc の -MM オプションを利用して、そのファイルが include しているファイルを調べてくれる。つまり、自分でヘッダファイルの依存を調べる必要が全くない。もし /include/foo.h が /include/bar.h を include していたとしても、その場合は自動的に 「foo_a.c は bar.h にも依存している」と解析してくれる。OMakeすごい!

この仕組みは決してC言語だけのアドホックな機能だけではなく、スキャナという依存関係を調べる一般的なフレームワークとして提供されている機能を利用して、予め実装されているだけである。

長くなってしまったけど、とりあえず上の2行を書くだけで、全ての依存関係をちゃんと解決して、ビルドできるようになったということだ。この状態で /src/ の中で

$ omake

というコマンドを実行すれば、ビルドが完了する。/include/foo.h を編集して omake を再び実行すれば、依存しているファイルが更新されたと認識され、再度ビルドが行われる。OMakeすごい!

テストプログラムをビルドする

/test 以下にも、 /test/OMakefile というファイルを作ろう。そろそろ勘づくかもしれないけど、OMakeでは全てのサブディレクトリにOMakefileを置いておく必要がある(らしい)。

/test/OMakefile には次のように記述する。

CFLAGS += -g -I ../include
LIBS += ../src/libfoo
.DEFAULT: $(CProgram testfoo, testfoo)

これだけ。 omake コマンドを実行すると、testfoo という実行ファイルができる。

変数 LIBS には、プロジェクト中にあるスタティックライブラリで、依存するものを書くらしい。../src/libfoo と書いておくと、/src/libfoo.a に依存することをOMakeに伝えられるので、libfoo.a が依存する(たとえば foo_a.c とか foo.h)が更新されたときは、ちゃんとテストプログラムの再ビルドが行われる。OMakeすごい!

このルールが最もいい書き方なのかどうか、はやみずもOMakeを勉強しはじえたばかりなのでわからないけれど、それにしてもシンプルであるということは同意してもらえると思う。

これでOMakeのルール記述は全部完了。




ここからが本題です

継続監視ビルドで悦楽の境地へ

プロジェクトのルートディレクトリで

$ omake -P

というコマンドを実行してみると "*** omake: polling for filesystem changes" というメッセージが出てプログラムがファイルの変更の監視をはじめる。

たとえばここで、 /include/bar.h を変更してみる。すると

  • ライブラリ libfoo.a を再ビルド ( libfoo.a → foo_a.c → foo.h → bar.h という依存)
  • テストプログラム testfoo を再ビルド ( testfoo → testfoo.c → libfoo.a → ・・・ → bar.h という依存 )

ということがおこる。OMakeすごい!

まとめ

OMakeのすごいところ

  • 依存関係の解析がすごい
    • デフォルトで C, OCaml 向けなどの便利な機能が用意されている
    • 依存関係を "ビルド時に調べる" ことができるスキャナ機能。自分で利用することももちろん可能
  • プロジェクトのディレクトリツリーに渡ったビルドがどこからでもできる
  • 継続監視ビルドで自動的にビルド
    • なんども make って叩かなくてもいいよ!

OMakeのイケてないところ

  • 検索しにくい
    • おまけじゃないよ!!!
    • "ocaml omake" でググると幸せになれる
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • デフォルトで入ってる環境が(ほどんと)無い
  • ・・・