.a ファイルのビルドを makefile と build.ninja で比較。 make から ninja への移行検討メモ


これは何

GNU make から ninja に移行を検討中のメモです。

想定読者

makefile を自分でかける人
.PHONY: clean を毎回書くのに飽き飽きした人
素の make の動作が遅いなぁと思う人
ninja 向けのメタビルドツールを作りたい人

ninja って何?

https://mattn.kaoriya.net/software/ninja/20140121141906.htm こことか読んでください。

libfoo.a のビルドを比較

a.c, b.c, c.c の3ファイルから構成される libfoo.a をビルドする例を makefilebuild.ninja で示します。
実行ファイルのビルドについては 前回を見てください。

今回は、ソースファイルの置き場と、ビルドの作業領域が別ディレクトリとしています。下記のようなファイル配置です。

./
|-- src/a.c
|-- src/b.c
|-- src/c.c
|-- build/makefile
`-- build/build.ninja

make する場合は、 make -C build を行います。 ninja する場合は ninja -C build となります。

makefile の場合

makefile の例を示します。デフォルトルールを全力で利用した場合、下記のようになると思います。
並列ビルドを行うと、 libfoo.a が破壊されるので実用性は皆無です。デフォルトルールを使わない方が良いでしょう。

makefile
CFLAGS = -Wall -Os -MMD -MP
CPPFLAGS = -DNDEBUG

vpath %.c ../src

.PHONY:clean
libfoo.a: libfoo.a(a.o) libfoo.a(b.o) libfoo.a(c.o)
libfoo.a(a.o):
libfoo.a(b.o):
libfoo.a(c.o):

clean:
    $(RM) libfoo.a a.o a.d b.o b.d c.o c.d
-include a.d
-include b.d
-include c.d

相変わらず、clean ルールの記述が面倒くさい。
初回 make は、中間ファイルが削除されるので、ちょっと気持ちがいい。
しかし、このルール記述だと毎度 ar が走るので、イマイチです。

ninja.build for gcc の場合

上記 makefile と同等の build.ninja を示します。 ninja は、暗黙のルールや変数を持ちません。
そのため、すべて明示的に定義しています。

build.ninja
cc = cc
cflags = -Wall -Os
cppflags = -DNDEBUG
ar = ar
arflags = rv

rule compile
     deps    = gcc
     depfile = $out.d
     command = $cc -MMD -MP -MF $out.d $cflags $cppflags $target_arch -c -o $out $in

rule archive
     command = $ar $arflags $out $in

build libfoo.a: archive a.o b.o c.o
build a.o: compile ../src/a.c
build b.o: compile ../src/b.c
build c.o: compile ../src/c.c

今回は、makefile と記述量があまり変わりません。変わらない理由は、makefile で変数展開を駆使していないからですが。。。
rule文とbuild文を明確に分けて記述するだけなので、読む側からは理解しやすいです。
makefile だと、サフィックスルールの記述方法が2種類あって、どっちがどうだったとか、ライブラリ用の書き方がどうだったかとかで混乱します。
そして、clean がビルトインされているのがうれしい。

感想

make は暗黙ルールを使わないと負けた気がして、無理矢理使ってしまいがちです。私だけかもしれませんが。
とは言え、暗黙ルールと暗黙の変数を使用しておかないと、環境変数で CLFAGSCC をオーバーライドしたりできなくなってしまうので、使わないとダサい makefile になっちゃいます。
暗黙ルールに従った makefile であれば、 gcc から clang に変えたければ export CC=clang してから make すれば良いので便利。クロスコンパイルしたければ、export CC=arm-linux-gcc してから make すれば OK だったりして便利。
といっても、こんな簡単にいかないから autotools や cmake が流行っている訳で。

思い返すと、 makefile を初めて書いたとき、build.ninja のように全てのルールと依存関係を記述していた。慣れてきて、暗黙ルールを駆使して記述量が減ることに喜んでいました。しかしなぁ、全部書くのと機能に違いが無いんだから、書きゃいいよなぁ。昔と違って、エディタもPCリソースも豊かなんだし。ということで、ninja がいいと思います。

参考リンク

The Ninja build system
Static library built-in rule in GNU make