はじめに
Ninjaはもう一つのビルドシステムです。ファイルの相互依存関係を入力とし、それらのビルドを高速に実行します。
Ninjaは他のビルドシステムの仲間です。Ninjaの特徴は高速であることです。Chromiumブラウザプロジェクトから生まれたものです。3万以上のソースファイルを含むプロジェクトで他のビルドシステム(非再帰型のカスタムMakefileから構築したものを含む)を使用すると、一つのファイルを変更するとビルド開始までに10秒かかってしまいます。Ninjaは1秒以下です。
哲学的概観
他のビルドシステムが高級言語であるのに対し、Ninjaはアセンブラを目指したものです。
ビルドシステムは、決定を下す必要があるときに遅くなります。編集-コンパイルサイクルにあるときは、できるだけ速くしたいものです。ビルドシステムには、直ちにビルドする必要があるものを把握するために必要な最小限の作業をしてもらいたいのです。
Ninjaは、任意の依存関係グラフを記述するために必要な最低限の機能しか備えていません。また、構文がないため、複雑な判断を表現することは不可能です。
その代わり、Ninja は入力ファイルを生成する別のプログラムと共に使用されることを意図しています。生成プログラム(autotoolsプロジェクトにある./configureのようなもの)は、システムの依存関係を分析し、できるだけ多くの決定を前もって行い、インクリメンタル・ビルドを速く維持することができる。autotools を超えて、「どのコンパイラ・フラグを使うべきか」、「デバッグ・モードかリリース・モードのバイナリをビルドすべきか」といったビルド時の決定も .ninja ファイル・ジェネレータに属します。
デザイン目標
ここでは、Ninjaのデザイン目標を紹介します。
- 非常に大規模なプロジェクトであっても、非常に高速に(即座に)インクリメンタル・ビルドを行うことができます。
- コードのビルド方法に関するポリシーはほとんどありません。プロジェクトや上位のビルドシステムによって、コードのビルド方法に関する意見は異なります。たとえば、ビルドされたオブジェクトはソースと一緒に保存すべきか、すべてのビルド出力は別のディレクトリに移動すべきか、などです。プロジェクトの配布可能なパッケージをビルドする「パッケージ」ルールはあるのでしょうか?これらの決定を回避するには、どちらかを選ぶのではなく、たとえ冗長性が増すとしても、どちらかを実装できるようにします。
- 依存関係を正しく取得し、Makefile で正しく取得するのが難しい特定の状況 (例えば、出力を生成するために使用するコマンドラインには暗黙の依存関係が必要です。C ソースコードをビルドするには、ヘッダの依存関係に対して gcc の -M フラグを使用する必要があります) で使用します。
- 利便性とスピードが相反する場合は、スピードを優先する。
非目標を明示したものもある。
- ビルドファイルを手書きするための便利な構文です。忍者ファイルは別のプログラムを使って生成する必要があります。こうすることで、多くの方針決定を回避することができるのです。
- ビルトインルールNinjaはC言語のコンパイルなどに関するルールを持たない。
- ビルド時のカスタマイズ。オプションは、忍者ファイルを生成するプログラムに属します。
- 条件分岐や探索経路のようなビルド時の意思決定能力。意思決定は遅い。
Ninjaが他のビルドシステムより高速なのは、非常にシンプルであるためです。プロジェクトの.ninjaファイルを作成するときに、Ninjaに何をすべきか正確に伝えなければなりません。
Makeとの比較
Ninjaは、Makeに最も近い精神と機能を持ち、ファイルのタイムスタンプ間の単純な依存関係に依存しています。
しかし、基本的にmakeは多くの機能を備えています。接尾辞のルール、関数、ソースをビルドする際にRCSファイルを検索するなどの組み込みルールなどです。Makeの言語は、人間が書くことを前提に設計されています。多くのプロジェクトでは、ビルドの問題はmakeだけで十分だと考えています。
これに対し、Ninjaはほとんど機能を持たず、ビルドを正しく行うために必要な機能だけを持ち、ほとんどの複雑な機能はNinjaの入力ファイルの生成に委ねられています。Ninja単体では、ほとんどのプロジェクトで役に立たないと思われます。
ここでは、Ninja が Make に追加する機能のいくつかを紹介します。(この種の機能は、より複雑な Makefile を使って実装できることが多いのですが、これらは Make 自体の一部ではありません)。
- Ninjaはビルド時に余分な依存関係を発見する特別なサポートを持っており、C/C++コードのheader dependencies を正しく取得することが容易にできます。
- ビルドエッジは、複数の出力を持つことができる。
- 出力は、それを生成するために使用されたコマンドラインに暗黙的に依存します。これは、例えばコンパイルフラグを変更すると、出力が再構築されることを意味します。
- 出力ディレクトリは、それに依存するコマンドを実行する前に、常に暗黙のうちに作成されます。
- ルールは、実行されるコマンドを短く記述することができるので、ビルド中に長いコマンドラインの代わりに、例えばCC foo.oを表示させることができます。
- ビルドは常に並列で実行され、デフォルトではシステムが持つCPUの数に基づいて実行されます。ビルドの依存関係の指定が不十分な場合、ビルドが正しく行われません。
- コマンドの出力は常にバッファリングされます。つまり、並列に実行されているコマンドは、その出力が混在することはなく、コマンドが失敗したときに、その失敗の出力を、失敗を生じさせたコマンドライン全体の横に表示することができます。