はじめに
Ninjaも数あるビルドシステムの一つです。ファイル間の依存関係(通常はソースコードと出力される実行ファイル)を入力として受け取り、それらを素早くビルドするよう処理を調整します。
Ninjaは多くのビルドシステムの中でも、速さを目標としている点が特徴です。Ninjaは私がChromiumブラウザープロジェクトで行った作業から生まれました。このプロジェクトには3万個を超えるソースファイルがあり、他のビルドシステム(独自の再帰しないMakefileで構築したものを含む)では、1ファイルを変更してからビルドを開始するまで10秒かかっていました。Ninjaなら1秒未満です。
設計思想の概要
他のビルドシステムを高水準言語にたとえるなら、Ninjaはアセンブラーを目指しています。
ビルドシステムは判断を行う必要があると遅くなります。編集とコンパイルを繰り返すときは、できるだけ速く動いてほしいものです。つまり、直ちにビルドが必要なものを特定するために、必要最小限の作業だけを行ってほしいのです。
Ninjaは任意の依存グラフを記述するための最小限の機能を備えています。構文が限られているため、複雑な判断を表現することはできません。
Ninjaは、入力ファイルを生成する別のプログラムと組み合わせて使うことを想定しています。生成プログラム(autotoolsプロジェクトにある./configureのようなもの)は、システムの依存関係を解析し、できる限り多くの判断を事前に済ませて、差分ビルドの速さを保てます。autotoolsよりさらに踏み込んで、「どのコンパイラーフラグを使うか」「デバッグ版とリリース版のどちらのバイナリーをビルドするか」といったビルド時の判断も、.ninjaファイルの生成プログラムで行うべきです。
設計目標
Ninjaの設計目標は次のとおりです。
-
非常に大きなプロジェクトでも、差分ビルドが非常に速い(つまり即座に実行される)こと。
-
コードのビルド方法について、ほとんど方針を押し付けないこと。プロジェクトや上位のビルドシステムごとに、望ましいビルド方法は異なります。たとえば、ビルドしたオブジェクトをソースの隣に置くか、すべてのビルド出力を別のディレクトリに置くか。プロジェクトの配布用パッケージをビルドする「package」ルールを用意するか。どちらかを選ぶのではなく、多少記述が長くなっても両方を実装できるようにして、こうした判断を避けます。
-
依存関係を正しく扱うこと。特に、Makefileでは正しく扱いにくい場合も対象にします。たとえば、出力には、その生成に使ったコマンドラインへの暗黙の依存関係が必要です。また、Cソースをビルドするには、ヘッダーの依存関係のためにgccの-Mフラグを使う必要があります。
-
便利さと速さが両立しない場合は、速さを優先すること。
明示的に目標としていないこともあります。
-
ビルドファイルを手書きするための便利な構文。Ninjaファイルは別のプログラムで生成してください。それにより、多くの方針判断を避けられます。
-
組み込みルール。Ninjaには、たとえばCコードのコンパイル用のルールは最初から用意されていません。
-
ビルド時のビルド設定の変更。オプションはNinjaファイルを生成するプログラムで扱うものです。
-
条件分岐や検索パスなど、ビルド時に判断する機能。判断には時間がかかります。
言い換えると、Ninjaが他のビルドシステムより速いのは、極端に単純だからです。プロジェクトの.ninjaファイルを作るときに、実行すべきことを正確にNinjaへ伝えなければなりません。
Makeとの比較
Ninjaは、ファイルのタイムスタンプ間の単純な依存関係に基づく点で、思想も機能もMakeに最も近いものです。
ただし、makeには本質的に多くの機能があります。サフィックスルール、関数、ソースのビルド時にRCSファイルを探すような組み込みルールなどです。Makeの言語は人間が書くことを想定して設計されています。多くのプロジェクトでは、makeだけでビルドの課題を十分に解決できます。
それに対し、Ninjaにはほとんど機能がありません。正しいビルドに必要なものだけを備え、複雑さの大部分をNinja入力ファイルの生成へ委ねています。多くのプロジェクトでは、Ninjaだけを使っても役立つとは限りません。
NinjaがMakeに追加する機能をいくつか挙げます。(こうした機能は、より複雑なMakefileで実装できることもありますが、makeそのものの機能ではありません。)
-
Ninjaはビルド時に追加の依存関係を見つけるための特別な機能を備えており、C/C++コードのヘッダー依存関係を正しく扱いやすくなっています。
-
一つのビルドエッジが複数の出力を持てます。
-
出力は、その生成に使ったコマンドラインに暗黙に依存します。そのため、たとえばコンパイルフラグを変更すると、出力が再ビルドされます。
-
出力ディレクトリは、それを必要とするコマンドを実行する前に、必ず暗黙に作成されます。
-
ルールには実行するコマンドの短い説明を指定できます。そのため、ビルド中に長いコマンドラインの代わりに、たとえばCC foo.oと表示できます。
-
ビルドは常に並列で実行され、既定の並列数はシステムのCPU数に基づきます。ビルドの依存関係を十分に指定していないと、正しくないビルドになります。
-
コマンドの出力は常にバッファーに蓄えられます。そのため、並列実行中のコマンドの出力が混ざりません。また、コマンドが失敗したときには、その失敗時の出力と、それを生成した完全なコマンドラインを並べて表示できます。
プロジェクトでNinjaを使う
Ninjaは現在、Unix系システムとWindowsで動作します。Linuxで最も多くテストされており、性能もLinuxが最も良いですが、Mac OS XとFreeBSDでも問題なく動作します。
小さなプロジェクトでは、Ninjaによる速度の違いはほとんど分からないかもしれません。(ただし、小さなプロジェクトでも、Ninjaの限られた構文によってビルドルールが単純になり、ビルドが速くなることがあります。)言い換えると、今のプロジェクトの編集・コンパイルの所要時間に満足しているなら、Ninjaは役に立ちません。
Ninjaそのものより使いやすく、機能が豊富なビルドシステムは多数あります。例を挙げると、Ninjaの作者はtupビルドシステムがNinjaの設計に影響を与えたと述べ、redoの設計も非常に巧妙だと考えています。
Ninjaの利点は、より高度なメタビルドシステムと組み合わせて使うことで得られます。
- gn
-
Google Chromeとその関連プロジェクト(v8、node.js)、およびGoogle Fuchsiaのビルドファイルを生成するために使われているメタビルドシステムです。gnは、Chromeが対応するすべてのプラットフォーム向けにNinjaファイルを生成できます。
- CMake
-
広く使われているメタビルドシステムで、CMake 2.8.8以降ではLinux上でNinjaファイルを生成できます。より新しいCMakeは、WindowsとMac OS X上でのNinjaファイル生成にも対応しています。
- その他
-
Ninjaはpremakeのような他のメタビルドソフトウェアにも、うまく組み込めるはずです。このような取り組みを行ったら、ぜひ知らせてください。
Ninjaを実行する
ninjaを実行します。既定では、現在のディレクトリにあるbuild.ninjaというファイルを探し、更新が必要なすべてのターゲットをビルドします。ビルドするターゲット(ファイル)をコマンドライン引数で指定することもできます。
target^という特別な構文もあります。コマンドラインで指定したソースを含むルールが存在する場合、そのルールの最初の出力をターゲットとして指定します。たとえば、ターゲットをfoo.c^と指定すると、foo.oがビルドされます(ビルドファイルにそのターゲットがあると仮定します)。
ninja -hはヘルプを表示します。Ninjaのフラグの多くは、意図的にMakeに合わせてあります。たとえば、ninja -C build -j 20はbuildディレクトリへ移動し、20個のビルドコマンドを並列実行します。(なお、Ninjaはもともと既定でコマンドを並列実行するため、通常は-jを指定する必要はありません。)
GNU jobserverへの対応
バージョン1.13以降、NinjaのビルドはGNU Make jobserverのクライアントプロトコルに従えます。最上位のGNU Makeや、その他のjobserverプールの実装が制御する大きなビルドシステムの一部としてNinjaを呼び出すときに便利です。並列ビルドタスク間の連携を改善できるためです。
この機能は、次の条件を満たすと自動的に有効になります。
-
ドライラン(-nまたは--dry-run)が有効になっていないこと。
-
コマンドラインで並列ジョブ数(たとえば-j<COUNT>)を明示的に指定していないこと。
-
環境変数MAKEFLAGSが定義され、Windowsでは--jobserver-auth=SEMAPHORE_NAME、Posixでは--jobserver-auth=fifo:PATHによって有効なjobserverモードを指定していること。
この場合、Ninjaは既定の並列実行の実装の代わりに、jobserverのジョブ枠のプールを使って並列数を制御します。
このモードでも、負荷平均による制限(-l<count>を使用した場合)は引き続き適用されます。
|
重要
|
Posixでは、GNU Make 4.4以降が実装するFIFO方式のプロトコルだけに対応しています。パイプ方式のjobserverが使われている場合(MAKEFLAGSに--jobserver-auth=<read>,<write>が含まれる場合)、Ninjaはそれを検出して警告を表示しますが、それ以外の処理は行わず無視します。 |
環境変数
Ninjaは動作を制御する二つの環境変数に対応しています。NINJA_STATUSは、実行するルールの前に表示する進捗状況です。
次のプレースホルダーを使えます。
- %s
-
開始したエッジの数。
- %t
-
ビルドの完了までに実行する必要があるエッジの総数。
- %p
-
完了したエッジの割合。
- %r
-
現在実行中のエッジの数。
- %u
-
まだ開始していないエッジの数。
- %f
-
完了したエッジの数。
- %o
-
完了したエッジの、1秒あたりの全体的な処理数。
- %c
-
完了したエッジの、1秒あたりの現在の処理数(-jで指定した並列数、または既定の並列数の範囲での平均)。
- %e
-
経過時間(秒)。(Ninja 1.2以降で利用可能。)
- %E
-
残り時間(ETA、秒)。(Ninja 1.12以降で利用可能。)
- %w
-
[h:]mm:ss形式の経過時間。(Ninja 1.12以降で利用可能。)
- %W
-
[h:]mm:ss形式の残り時間(ETA)。(Ninja 1.12以降で利用可能。)
- %P
-
予測した総実行時間に対する経過時間の割合(ppp%形式)。(Ninja 1.12以降で利用可能。)
- %%
-
通常の%文字。
既定の進捗表示は"[%f/%t] "です(ビルドルールとの区切りのため、末尾に空白がある点に注意してください)。他の例として、"[%u/%r/%f] "という進捗表示も考えられます。
環境変数MAKEFLAGSが定義されていると、Ninjaの並列ビルドコマンドの割り当て方法が変わることがあります。詳しくはGNU jobserverへの対応の節を参照してください。
追加ツール
Ninjaのコマンドラインにある-tフラグは、Ninjaの開発中に便利だと分かったツールを実行します。現在のツールは次のとおりです。
|
query
|
指定したターゲットの入力と出力を表示します。 |
|
browse
|
Webブラウザーで依存グラフを閲覧します。ファイルをクリックすると、そのファイルに表示を絞り、入力と出力を表示します。この機能にはPythonのインストールが必要です。既定ではポート8000を使い、Webブラウザーを開きます。次のように変更できます。 |
|
graph
|
自動グラフ配置ツールgraphvizが使う構文でファイルを出力します。次のように使います。 Ninjaのソースツリーでは、ninja graph.pngでNinja自身のグラフ画像を生成できます。ターゲットを指定しない場合は、すべてのルートターゲットのグラフを生成します。 |
|
targets
|
ルール別または深さ別にターゲットの一覧を出力します。ninja -t targets rule nameのように使うと、指定したルールでビルドするターゲットの一覧を表示します。ルールを指定しない場合は、ソースファイル(グラフの葉)を表示します。ninja -t targets depth digitのように使うと、ルートターゲット(その先に出力のないターゲット)から深さ優先でターゲットの一覧を表示します。依存関係はインデントで示されます。深さが0ならすべてのターゲットを表示します。引数がない場合はninja -t targets depth 1として扱います。このモードでは、同じターゲットが複数回表示されることがあります。ninja -t targets allを使うと、利用可能なすべてのターゲットをインデントなしで表示し、depthモードより速く動作します。 |
|
commands
|
ターゲットの一覧を指定すると、すべての出力ファイルに更新が必要であると仮定した場合に、それらのターゲットを再ビルドするために順番に実行できるコマンドの一覧を表示します。 |
|
inputs
|
ターゲットの一覧を指定すると、それらの再ビルドに使うすべての入力の一覧を表示します。Ninja 1.11以降で利用可能です。 |
|
multi-inputs
|
ターゲットのビルドに必要な入力の集合を一つ以上表示します。各行は、ターゲット、区切り文字列、入力、終端文字で構成されます。この一覧は、ある入力がどのターゲットに影響するかを知りたいときに便利です。 出力は、次の要素からなる行の列になります。 既定の<delimiter>はTAB文字一つです。--delimiter引数で任意の文字列に変更できます。 既定の<terminator>は行の終端文字です(Posixでは\n、Windowsでは\r\n)。--print0引数を使うと、終端文字を\0に変更できます。 multi-inputsツールの使用例です。 multi-inputsツールが生成する出力の例です。 同じ入力を複数のターゲットが使う場合、その入力は複数のターゲットについて表示されることに注意してください。 Ninja 1.13以降で利用可能です。 |
|
clean
|
ビルドしたファイルを削除します。既定では、生成プログラムが作成したファイルを除き、ビルドしたすべてのファイルを削除します。-gフラグを加えると、生成プログラムが作成したビルド済みファイルも削除します(ルールリファレンスのgenerator属性を参照してください)。追加の引数はターゲットです。指定したターゲットと、それらのためにビルドしたすべてのファイルを再帰的に削除します。 ninja -t clean -r rulesのように使うと、指定したルールでビルドしたすべてのファイルを削除します。 作成されていてもグラフで参照されていないファイルは削除しません。このツールは-vと-nのオプションも考慮します(-nは-vも意味する点に注意してください)。 |
|
cleandead
|
以前のビルドで生成されたものの、現在のビルドファイルには含まれなくなったファイルを削除します。Ninja 1.10以降で利用可能です。 |
|
compdb
|
ルールの一覧を指定すると、Clangのツールインターフェースが想定するJSON形式のコンパイルデータベースを標準出力へ出力します。各ルールは、最初の入力がソースファイル名であるC系言語のコンパイラールールであると想定します。Ninja 1.2以降で利用可能です。 |
|
compdb-targets
|
compdbに似ていますが、ルールの代わりにターゲットの一覧を受け取り、少なくとも一つのターゲットが必要です。生成されるコンパイルデータベースには、指定したターゲットのビルドに必要なすべてのコマンド、かつそれらのコマンドだけが含まれます。 |
|
deps
|
.ninja_depsファイルに保存されたすべての依存関係を表示します。ターゲットを指定すると、そのターゲットの依存関係だけを表示します。Ninja 1.4以降で利用可能です。 |
|
missingdeps
|
ターゲットの一覧を指定すると、生成されたファイルに依存しているにもかかわらず、その生成プログラムへの適切な依存関係(推移的なものを含む)がないターゲットを探します。そのようなターゲットは、クリーンビルドが不安定になる原因になります。 depsログまたはdepfileの依存関係情報が正しければ、このような不正なターゲットを見つけられます。生成されたファイル(生成ターゲットの出力)に暗黙に依存している一方で、生成ターゲットまでの明示的依存関係または順序のみの依存関係の経路がないターゲットは、不正と判断されます。 このツールの結果は、他のターゲットを一切ビルドせず、クリーンな出力ディレクトリで一覧のターゲットだけをビルドしてみることで確認できます。各ターゲットは、生成されたファイルについてのインクルード欠落エラー、または同等のエラーで失敗するはずです。Ninja 1.11以降で利用可能です。 |
|
recompact
|
.ninja_depsファイルを再コンパクト化します。Ninja 1.4以降で利用可能です。 |
|
restat
|
.ninja_logファイルに記録された、すべてのファイルの更新時刻を更新します。Ninja 1.10以降で利用可能です。 |
|
rules
|
すべてのルールの一覧を出力します。ninja -t targets rule nameまたはninja -t compdbに渡すルール名を調べるために使えます。-dフラグを加えると、ルールの説明も表示します。 |
|
msvc
|
Windowsホストだけで利用できます。事前に定義した環境変数の組を使い、次のようにcl.exeコンパイラーを呼び出す補助ツールです。 ENVFILEは、WindowsのCreateProcessA()に適した環境ブロックを含むバイナリーファイルです。つまり、NAME=VALUEのような形のゼロ終端文字列を連ね、最後に追加のゼロ終端を置いたものです。ローカルのコードページのエンコーディングを使うことに注意してください。 このツールは、/showIncludesフラグ使用時のコンパイラー出力を解析し、GCC互換のdepfileを生成する、非推奨の方法にも対応しています。 このオプションでは、-p STRINGを使い、cl.exeが依存関係情報の出力に使う、ローカライズされた行頭の文字列を渡せます。英語圏では、二重引用符を除いた"Note: including file: "ですが、他の地域では異なります。 現在のNinjaは、Ninjaファイル内のdeps = msvcとmsvc_deps_prefixを使い、この機能に直接対応しています。この直接対応では、コンパイラーを呼ぶたびに追加のツールプロセスを起動する必要もなく、Windowsでのビルドが大幅に速くなることがあります。 |
|
wincodepage
|
Windowsホストで利用できます(Ninja 1.11以降)。ビルドファイルに想定されるエンコーディングのWindowsコードページを表示します。出力は次の形式です。 Ninjaの将来のバージョンでは、追加の行が出力される場合があります。 <codepage>は、次のいずれかです。
|
独自のNinjaファイルを書く
このマニュアルの残りは、Ninjaファイルを自分で構築する場合にだけ役立ちます。たとえば、メタビルドシステムを書いたり、新しい言語に対応したりする場合です。
概念の概要
Ninjaはファイル間の依存グラフを評価し、ファイルの更新時刻から判断してビルドターゲットを最新にするために必要なコマンドを実行します。Makeを知っているなら、Ninjaは非常によく似ています。
ビルドファイル(既定の名前はbuild.ninja)には、コンパイラーの実行方法など、長いコマンドの短い名前であるルールの一覧と、ルールを使ったファイルのビルド方法を示すbuild文の一覧が含まれます。build文は、どのルールをどの入力へ適用し、どの出力を生成するかを指定します。
概念上、build文はプロジェクトの依存グラフを記述し、rule文は、グラフのあるエッジに沿ってファイルを生成する方法を記述します。
構文の例
構文の大部分を示す、基本的な.ninjaファイルを挙げます。以降の節では、このファイルを例として使います。
cflags = -Wall
rule cc
command = gcc $cflags -c $in -o $out
build foo.o: cc foo.c変数
手書きの便利さは目標にしていませんが、ビルドファイルを読みやすく(デバッグしやすく)するため、Ninjaでは文字列に短く再利用可能な名前を付けられます。次のような宣言を、
cflags = -g等号の右辺でドル記号によって参照できます。たとえば次のように使います。
rule cc
command = gcc $cflags -c $in -o $out${in}のように、波括弧を使って変数を参照することもできます。
変数は変更できず、シャドーイングしかできないため、「束縛」と呼ぶ方が適切かもしれません。シャドーイングの仕組みについては、この文書の後半で詳しく説明します。
ルール
ルールはコマンドラインの短い名前を宣言します。ruleキーワードとルール名からなる行で始まり、その後にインデントしたvariable = valueの行を並べます。
前述の基本例では、ccという新しいルールを、実行するコマンドとともに宣言しています。ルール内では、command変数が実行するコマンドを定義し、$inはそのコマンドの入力ファイルの一覧(foo.c)へ、$outは出力ファイルの一覧(foo.o)へ展開されます。特殊変数の完全な一覧は、リファレンスを参照してください。
build文
build文は、入力ファイルと出力ファイルの関係を宣言します。buildキーワードで始まり、build outputs: rulename inputsという形式です。この宣言は、すべての出力ファイルが入力ファイルから生成されることを示します。出力ファイルが存在しないか入力が変わったとき、Ninjaはルールを実行し、出力を再生成します。
前述の基本例では、ccルールを使ってfoo.oをビルドする方法を記述しています。
buildブロックのスコープ内(関連するruleの評価時も含む)では、$in変数は入力の一覧、$out変数は出力の一覧です。
build文の後にも、ルールと同様に、インデントしたkey = valueの組を並べられます。これらの変数は、コマンド内の変数を評価するとき、他の変数をシャドーイングします。たとえば次のとおりです。
cflags = -Wall -Werror
rule cc
command = gcc $cflags -c $in -o $out
# If left unspecified, builds get the outer $cflags.
build foo.o: cc foo.c
# But you can shadow variables like cflags for a particular build.
build special.o: cc special.c
cflags = -Wall
# The variable was only shadowed for the scope of special.o;
# Subsequent build lines get the outer (original) cflags.
build bar.o: cc bar.cスコープの仕組みについて詳しくは、リファレンスを参照してください。
build文からルールへ、より複雑な情報(たとえば「最初の入力のファイル拡張子」)を渡す必要がある場合は、前述のcflagsのように、追加の変数として渡してください。
最上位のNinjaファイルがいずれかのbuild文の出力に指定されており、更新が必要な場合、Ninjaはユーザーが要求したターゲットをビルドする前に、そのファイルを再ビルドして再読み込みします。
コードからNinjaファイルを生成する
Ninjaの配布物にあるmisc/ninja_syntax.pyは、Ninjaファイルの生成を簡単にする小さなPythonモジュールです。ninja.rule(name='foo', command='bar', depfile='$out.d')のようにPythonから呼び出すと、適切な構文を生成します。役に立つなら、プロジェクトのビルドシステムへそのまま組み込んでかまいません。
より詳しい説明
phonyルール
特別なルール名phonyを使うと、他のターゲットに別名を付けられます。たとえば次のとおりです。
build foo: phony some/file/in/a/faraway/subdir/fooこれにより、ninja fooで長いパスのファイルをビルドできます。意味上、phonyルールはcommandが何もしない通常のルールと同じですが、phonyルールは特別に扱われます。実行時に表示されず、ログにも記録されず(後述)、ビルド中に表示されるコマンド数にも含まれません。
phonyターゲットが別のビルドルールの入力に使われると、そのビルドルールは、意味上、phonyルールの入力を自身の入力として扱います。そのため、phonyルールで、ヘッダーファイルなどの入力をまとめられます。
phonyは、ビルド時には存在しないかもしれないファイルのダミーターゲットを作るためにも使えます。依存関係のないphonyのbuild文を記述した場合、そのターゲットが存在しなければ更新が必要と判断されます。phonyのbuild文がない場合、ビルドに必要なファイルが存在しなければ、Ninjaはエラーを報告します。
再ビルドされないルールを作るには、入力のないビルドルールを使います。
rule touch
command = touch $out
build file_that_always_exists.dummy: touch
build dummy_target_to_follow_a_pattern: phony file_that_always_exists.dummy既定のターゲットを指定する文
コマンドラインにターゲットの指定がない場合、既定では、他の場所で入力として指定されていないすべての出力をNinjaがビルドします。default文を使うと、この動作を変更できます。default文を指定すると、コマンドラインでターゲットを指定しなかった場合に、出力ファイルの指定した一部だけをビルドします。
default文はdefaultキーワードで始まり、default targetsという形式です。ターゲットを出力ファイルとして宣言するbuild文の後に置く必要があります。default文は累積するため、複数の文で既定のターゲットの一覧を拡張できます。たとえば次のとおりです。
default foo bar
default bazこれにより、Ninjaは既定でfoo、bar、bazターゲットをビルドします。
Ninjaのログ
Ninjaは、ビルドしたファイルごとに、そのビルドに使ったコマンドをログに記録します。このログを使うことで、既存の出力がビルドファイルに指定されたものとは異なるコマンドラインでビルドされたこと(つまりコマンドラインが変わったこと)を検出し、そのファイルを再ビルドすべきだと判断できます。
ログはビルドのルートにある.ninja_logというファイルへ保存します。最も外側のスコープにbuilddirという変数を指定すると、.ninja_logは代わりにそのディレクトリへ保存されます。
バージョン互換性
Ninja 1.2以降で利用可能です。
Ninjaのバージョン表記は、標準的なmajor.minor.patch形式に従います。後方互換性のない構文や動作の変更ではメジャーバージョンが、新しい動作の追加ではマイナーバージョンが上がります。build.ninjaにはninja_required_versionという変数を宣言し、生成したファイルを使うために必要なNinjaの最低バージョンを指定できます。たとえば、
ninja_required_version = 1.1この宣言は、ビルドファイルがNinja 1.1で導入された機能(たとえばpool構文)に依存し、ビルドにNinja 1.1以降が必要であることを示します。他のNinja変数と異なり、このバージョン要件は、解析中に変数を見つけた時点ですぐに確認します。そのため、ビルドファイルの先頭に置くのが適切です。
Ninja自身とninja_required_versionのメジャーバージョンが一致しない場合、Ninjaは必ず警告します。メジャーバージョンの変更はまだ起きていないため、どのような動作が必要になるかを予測するのは困難です。
C/C++のヘッダー依存関係
Ninjaには、C/C++のヘッダー依存関係(または同様の仕組みのビルド依存関係)を正しく扱うための追加機能があります。
ヘッダーの問題は、あるソースファイルが依存するファイルの完全な一覧を、コンパイラーだけが発見できることです。プリプロセッサーの定義やインクルードパスが変わると、使われるファイルも変わります。一部のコンパイラーはビルド中にこの情報を出力でき、Ninjaはそれを使って依存関係を正確に扱えます。
ファイルがまだコンパイルされたことがなければ、いずれにしてもビルドが必要で、その副作用としてヘッダー依存関係が生成されます。その後に何らかのファイルが変更されると(依存するヘッダーの組が変わるような変更であっても)、その変更によって再ビルドされ、依存関係も最新に保たれます。
こうした特殊な依存関係を読み込むとき、Ninjaは、列挙された依存ファイルが存在しなくてもエラーにならないよう、追加のビルドエッジを暗黙に作成します。これにより、ヘッダーファイルを削除して再ビルドしても、入力の欠落によってビルドが中断されません。
depfile
gcc(およびclangなどの他のコンパイラー)は、Makefileの構文で依存関係情報を出力できます。(gccに限らず、この形式で依存関係を書き出せるコマンドなら使えます。)
この情報をNinjaへ取り込むには、連携が必要です。Ninja側では、buildのdepfile属性に、このデータを書き出すパスを指定しなければなりません。(Ninjaが対応するのは、コンパイラーが出力するMakefile構文の限られた部分だけです。)コマンド側も、依存関係をdepfileのパスへ書き出す必要があります。次の例のように使います。
rule cc
depfile = $out.d
command = gcc -MD -MF $out.d [other gcc flags here]gccの-MDフラグはヘッダー依存関係の出力を指示し、-MFフラグはその出力先を指示します。
deps
(Ninja 1.3以降で利用可能。)
大きなプロジェクトでは、これらの依存関係ファイルを起動時に読み込む処理が遅くなることが分かりました。ファイルシステムが遅いWindowsでは、特に顕著です。
Ninja 1.3では、依存関係が生成された直後に処理し、同じ情報を小さくまとめた形式でNinja内部のデータベースへ保存できます。
Ninjaは二つの形式でこの処理に対応しています。
-
deps = gccは、ツールがMakefile形式でgcc方式の依存関係を出力することを指定します。前述の例にこれを加えると、コンパイル終了直後にdepfileを処理し、その後、.dファイルを削除します(このファイルは一時的に使うだけです)。
-
deps = msvcは、ツールがVisual Studioコンパイラーの/showIncludesフラグの形式でヘッダー依存関係を出力することを指定します。つまり、ツールは特別な書式の行を標準出力へ出力し、Ninjaはそれらの行を表示する出力から取り除きます。depfile属性は不要ですが、ヘッダーファイルのパスの前に付くローカライズされた文字列を、全体で定義する必要があります。たとえば英語版Visual Studioでは、msvc_deps_prefix = Note: including file:です(これが既定値です)。
msvc_deps_prefix = Note: including file: rule cc deps = msvc command = cl /showIncludes -c $in /Fo$out
インクルードディレクトリの指定に絶対パスを使うと、depfileに相対パスと絶対パスが混在する場合があります。他のビルドルールで使うパスと完全に一致させる必要があるため、このような場合は相対パスを使うことを推奨します。
プール
Ninja 1.1以降で利用可能です。
プールを使うと、一つ以上のルールやエッジに対し、既定の並列数より厳しく制限した、有限の並列ジョブ数を割り当てられます。
たとえば、大きな実行ファイルのリンク処理のような負荷の高い特定のルールや、並列実行すると性能が落ちることが分かっている特定のbuild文を制限するために使えます。
各プールには、ビルドファイル内で指定するdepth変数があります。ルールまたはbuild文のpool変数で、そのプールを参照します。
どのようなプールを指定しても、Ninjaが同時に実行するジョブ数は、既定の並列数またはコマンドラインの-jで指定したジョブ数を超えません。
# No more than 4 links at a time.
pool link_pool
depth = 4
# No more than 1 heavy object at a time.
pool heavy_object_pool
depth = 1
rule link
...
pool = link_pool
rule cc
...
# The link_pool is used here. Only 4 links will run concurrently.
build foo.exe: link input.obj
# A build statement can be exempted from its rule's pool by setting an
# empty pool. This effectively puts the build statement back into the default
# pool, which has infinite depth.
build other.exe: link input.obj
pool =
# A build statement can specify a pool directly.
# Only one of these builds will run at a time.
build heavy_object1.obj: cc heavy_obj1.cc
pool = heavy_object_pool
build heavy_object2.obj: cc heavy_obj2.cc
pool = heavy_object_poolconsoleプール
Ninja 1.5以降で利用可能です。
consoleという名前の、深さが1の定義済みプールがあります。このプールのタスクは、Ninjaに渡された標準入力、標準出力、標準エラーのストリームへ直接アクセスできます。これらは通常、ユーザーのコンソールに接続されています(名前の由来でもあります)が、リダイレクトされることもあります。対話型のタスクや、テストスイートのようにコンソールへ状況を表示する長時間のタスクに便利です。
consoleプールのタスクが実行中は、Ninjaの通常の出力(進捗表示や並列タスクの出力など)は、そのタスクが完了するまでバッファーに蓄えられます。
Ninjaファイルのリファレンス
ファイルは宣言を並べたものです。宣言には次の種類があります。
-
ルール宣言。rule rulenameで始まり、その後に変数を定義するインデントした行を並べます。
-
ビルドエッジ。build output1 output2: rulename input1 input2という形式です。
末尾に| dependency1 dependency2を加えると、暗黙の依存関係を追加できます。
|| dependency1 dependency2を加えると、順序のみの依存関係を追加できます(依存関係の種類のリファレンスを参照)。|@ validation1 validation2を加えると、検証を追加できます(検証のリファレンスを参照)。暗黙の出力(Ninja 1.7以降で利用可能)は、:の前に| output1 output2を加えることで指定でき、$outには含まれません(出力の種類のリファレンスを参照)。
-
変数宣言。variable = valueという形式です。
-
既定のターゲットを指定する文。default target1 target2という形式です。
-
追加のファイルへの参照。subninja pathまたはinclude pathという形式です。違いは、後述のスコープの説明で解説します。
-
プール宣言。pool poolnameという形式です。プールについては、プールの節で説明しています。
字句構文
Ninjaは、パス内のスラッシュなど、Ninjaが解釈するバイトがASCIIであれば、ほとんどの場合エンコーディングを問いません。したがって、UTF-8やISO-8859-1などの入力ファイルも動作するはずです。
コメントは#で始まり、行末まで続きます。
改行には意味があります。build foo barのような文は、空白で区切られたトークンの列で、改行で終わります。トークン内の改行と空白はエスケープする必要があります。
エスケープ文字は$の一つだけで、次のように動作します。
- $の後に改行
-
改行をエスケープします(改行をまたいで現在の行を継続します)。
- $の後にテキスト
-
変数の参照です。
- ${varname}
-
$varnameの別の構文です。
- $の後に空白
-
空白になります。(空白がファイル名の区切りになるパスの一覧でだけ必要です。後述します。)
- $:
-
コロンになります。(コロンが出力の一覧を終わらせるbuild行でだけ必要です。)
- $$
-
通常の$文字になります。
build文とdefault文は、まず空白で区切られたファイル名の一覧として解析され、その後、各名前が展開されます。したがって、変数内の空白は、展開後のファイル名の中の空白になります。
spaced = foo bar
build $spaced/baz other$ file: ...
# The above build line has two outputs: "foo bar/baz" and "other file".name = value文では、値の先頭にある空白文字は常に取り除かれます。行継続後の行の先頭にある空白文字も取り除かれます。
two_words_with_one_space = foo $
bar
one_word_with_no_space = foo$
barその他の空白文字は、行頭にある場合だけ意味を持ちます。ある行が直前の行より深くインデントされている場合は、親のスコープの一部とみなします。直前の行より浅い場合は、直前のスコープを閉じます。
最上位の変数
最も外側のファイルスコープで宣言したときに意味を持つ変数は、二つあります。
ルールの変数
ruleブロックには、ルールの処理に影響するkey = value宣言の一覧が含まれます。特殊なキーの完全な一覧を示します。
- command(必須)
-
実行するコマンドラインです。各ruleに含められるcommand宣言は一つだけです。引用符や複数のコマンドの実行について詳しくは、次の節を参照してください。
- depfile
-
追加の暗黙の依存関係を含むMakefileのパスです。指定は任意です(依存関係の種類のリファレンスを参照)。これは明確にC/C++のヘッダー依存関係に対応するためのものです。詳しい説明を参照してください。
- deps
-
(Ninja 1.3以降で利用可能。)指定する場合、特殊な依存関係処理を指定するためにgccまたはmsvcのいずれかでなければなりません。詳しい説明を参照してください。生成されるデータベースは、builddir内に.ninja_depsとして保存されます。builddirの説明を参照してください。
- msvc_deps_prefix
-
(Ninja 1.5以降で利用可能。)msvcの/showIncludes出力から取り除く文字列を定義します。deps = msvcを使い、英語版でないVisual Studioを使う場合にだけ必要です。
- description
-
コマンドの実行中に見やすく表示するための、コマンドの短い説明です。-vフラグは、完全なコマンドと説明のどちらを表示するかを制御します。コマンドが失敗した場合は、完全なコマンドラインを、そのコマンドの出力より前に必ず表示します。
- dyndep
-
(Ninja 1.10以降で利用可能。)build文でだけ使います。指定する場合、build文の入力の一つを指定しなければなりません。そのファイルから、動的に発見した依存関係情報を読み込みます。詳しくは、動的依存関係の節を参照してください。
- generator
-
指定されている場合、このルールが生成プログラムを再実行するために使われることを示します。generatorルールでビルドしたファイルは、二つの点で特別に扱われます。第一に、コマンドラインが変わっても再ビルドされません。第二に、既定の削除対象になりません。
- in
-
このruleを参照するbuild行に入力として渡されたファイルの、空白で区切られた一覧です。コマンド内ではシェル向けにクォートされます。($inは便利さのためだけに用意されています。このファイル一覧の一部や変形した一覧が必要なら、それを含む新しい変数を作り、代わりに使ってください。)
- in_newline
-
複数の入力を空白ではなく改行で区切ることを除き、$inと同じです。($rspfile_contentで使います。入力処理に固定長バッファーを使うMSVCリンカーの不具合を回避するためのものです。)
- out
-
このruleを参照するbuild行に出力として渡されたファイルの、空白で区切られた一覧です。コマンド内ではシェル向けにクォートされます。
- restat
-
指定されている場合、コマンドの実行後に、Ninjaはコマンドの出力を再度statします。コマンドが更新時刻を変更しなかった出力は、最初からビルドする必要がなかったものとして扱います。その結果、その出力に依存するものが、実行待ちのビルド処理の一覧から取り除かれる場合があります。
- rspfile, rspfile_content
-
両方を指定すると、Ninjaはそのコマンドに応答ファイルを使います。つまり、コマンドを呼び出す前に、指定した文字列(rspfile_content)を指定したファイル(rspfile)へ書き込み、コマンドが正常終了した後にそのファイルを削除します。
これは、コマンドラインの最大長が制限されていて、代わりに応答ファイルを使う必要があるWindows OSで特に便利です。
次の例のように使います。
rule link command = link.exe /OUT$out [usual link flags here] @$out.rsp rspfile = $out.rsp rspfile_content = $in build myapp.exe: link a.obj b.obj [possibly many other .obj files]
command変数の解釈
Unix系システムとWindowsでは、コマンドラインの動作が根本的に異なります。
Unix系システムでは、コマンドは引数の配列です。Ninjaのcommand変数はそのままsh -cへ渡され、shがその文字列をargv配列へ解釈します。そのため、引用符の規則はシェルのものになります。複数のコマンドをつなぐ&&や、環境変数を設定するVAR=value cmdなど、通常のシェル演算子をすべて使えます。
Windowsではコマンドは文字列なので、Ninjaはcommand文字列を直接CreateProcessへ渡します。(単にコンパイラーを実行する一般的な場合には、オーバーヘッドが小さくなります。)したがって、引用符の規則は呼び出されたプログラムが決めます。Windowsでは通常、Cライブラリーがその規則を提供します。複数のコマンドをつなぐ&&の使用など、シェルによる解釈が必要な場合は、コマンドの先頭にcmd /cを付け、Windowsのシェルを実行させてください。Ninjaが「invalid parameter」エラーを出す場合は、通常、コマンドラインの長さの制限を超えたことを示します。
ビルドの出力
ビルドの出力には、わずかに異なる二つの種類があります。
-
明示的出力。build行に列挙する出力です。ルール内では$out変数として利用できます。
コンパイルコマンドのオブジェクトファイルなどに使う、標準的な出力の形式です。
-
暗黙の出力。build行の:より前に、| out1 out2という構文で列挙する出力です(Ninja 1.7以降で利用可能)。意味は明示的出力と同じですが、暗黙の出力が$out変数に含まれない点だけが異なります。
コマンドのコマンドラインには現れない出力を表現するためのものです。
ビルドの依存関係
ビルドの依存関係には、わずかに異なる三つの種類があります。
-
明示的依存関係。build行に列挙する依存関係です。ルール内では$in変数として利用できます。これらのファイルが変更されると、出力が再ビルドされます。これらのファイルが存在せず、Ninjaがビルド方法も知らない場合、ビルドは中断します。
コンパイルコマンドのソースファイルなどに使う、標準的な依存関係の形式です。
-
暗黙の依存関係。ルールのdepfile属性から取り込むか、build行の末尾の| dep1 dep2という構文で指定します。意味は明示的依存関係と同じですが、暗黙の依存関係が$in変数に含まれない点だけが異なります。
コマンドのコマンドラインには現れない依存関係を表現するためのものです。たとえば、固定したファイル名のファイルを読むスクリプトを実行するルールでは、そのファイルを暗黙の依存関係にするべきです。引数には現れなくても、そのファイルを変更したら出力を再ビルドする必要があるためです。
depfileから読み込む依存関係は、ルールリファレンスで説明しているとおり、意味がわずかに異なる点に注意してください。
-
順序のみの依存関係。build行の末尾に|| dep1 dep2という構文で指定します。これらの依存先に更新が必要な場合、そのビルドが終わるまで出力は再ビルドされません。ただし、順序のみの依存先の変更だけでは、出力の再ビルドは発生しません。
順序のみの依存関係は、ビルド時に初めて発見する依存関係の初期準備に便利です。たとえば、後続のコンパイル処理を始める前にヘッダーファイルを生成する場合です。(そのヘッダーがコンパイルで使われると、生成された依存関係ファイルが暗黙の依存関係を表すようになります。)
ファイルパスはそのまま比較します。したがって、同じファイルを指していても、絶対パスと相対パスはNinjaでは別のものとして扱います。
検証
Ninja 1.11以降で利用可能です。
build行が、コマンドラインで指定されたターゲットまたは既定のターゲットの推移的な依存先である場合、その行に列挙した検証のファイルを、ビルドグラフの最上位へ追加します。Ninjaのコマンドラインで指定した場合と同じ扱いになります。
検証は、build文の出力ファイルに更新が必要かどうかに関係なく、ビルドグラフへ追加されます。また、検証として使うファイルを出力するbuild文の更新の必要性は、その検証を要求したbuild文の更新の必要性に影響しません。
あるビルドエッジは、別のビルドエッジが自身に依存していても、そのエッジを検証として列挙できます。
検証は、静的解析ツールなど、エラーチェックを行うものの、ビルドに必要な生成物を出力しないルールを扱うために設計されています。静的解析ルールを、ソースファイルの主要なビルドルールや、そのルールに依存するルールの暗黙の入力にすると、ビルドのクリティカルパスが遅くなります。検証を使えば、主要なビルドルールが完了した後、ビルドの続きと静的解析ルールを並列に実行できます。
変数の展開
変数は、パス(build文またはdefault文の中)と、name = value文の右辺で展開されます。
name = value文を評価すると、その右辺は直ちに展開されます(後述のスコープ規則に従います)。それ以降、$nameは、展開結果の固定された文字列に展開されます。二重に展開されることを防ぐために、値を「二重にエスケープ」する必要はありません。
すべての変数は、解析中に現れた時点で直ちに展開されます。ただし、一つ重要な例外があります。ruleブロックの変数は、宣言時ではなく、ルールを使うときに展開されます。次の例では、demoルールは「this is a demo of bar」と表示します。
rule demo
command = echo "this is a demo of $foo"
build out: demo
foo = bar評価とスコープ
最上位の変数宣言のスコープは、宣言したファイルです。
ルール宣言のスコープも、宣言したファイルです。(Ninja 1.6以降で利用可能。)
別の.ninjaファイルを取り込むsubninjaキーワードは、新しいスコープを作ります。取り込まれたsubninjaファイルは、親ファイルの変数とルールを利用でき、自身のファイルスコープ内でその値をシャドーイングできますが、親ファイルの変数の値には影響しません。
Cの#include文のように、現在のスコープへ別の.ninjaファイルを取り込むには、subninjaではなくincludeを使います。
buildブロック内でインデントした変数宣言のスコープは、そのbuildブロックです。buildブロック(または、そのブロックで使うrule)で展開する変数の検索順序は、次のとおりです。
-
特殊な組み込み変数($in、$out)。
-
buildブロックのビルド単位の変数。
-
ruleブロックのルール単位の変数(つまり$command)。(前述の展開の説明のとおり、これらは「遅れて」展開され、$inのようにスコープ内にある束縛を利用できます。)
-
build行があるファイルの、ファイル単位の変数。
-
そのファイルをsubninjaキーワードで取り込んだファイルの変数。
動的依存関係
Ninja 1.10以降で利用可能です。
初回から正しくビルドするために、ビルド中にソースファイルの内容から暗黙の依存関係情報を動的に発見する必要がある場合があります。たとえば、Fortranモジュールの依存関係です。これは、2回目以降の正しい再ビルドにだけ必要なヘッダー依存関係とは異なります。build文には、入力の一つを指定するdyndep束縛を設け、そのファイルから動的依存関係情報を読み込む必要があることを指定できます。たとえば次のとおりです。
build out: ... || foo
dyndep = foo
build foo: ...これは、ファイルfooがdyndepファイルであることを指定します。入力なので、fooがビルドされる前にoutのbuild文を実行することはありません。fooが完成するとすぐに、Ninjaはそれを読み込み、outについて動的に発見した依存関係情報を取り込みます。追加の暗黙の入力や出力が含まれることもあります。Ninjaはそれに従ってビルドグラフを更新し、最初からその情報が分かっていたかのようにビルドを進めます。
Dyndepファイルのリファレンス
dyndep束縛で指定するファイルは、Ninjaビルドファイルと同じ字句構文を使い、次の構成になります。
-
<major>[.<minor>][<suffix>]という形式のバージョン番号。
ninja_dyndep_version = 1現在、バージョン番号は必ず1または1.0でなければなりませんが、任意の接尾辞を付けられます。
-
次の形式のbuild文を一つ以上。
build out | imp-outs... : dyndep | imp-ins...各文は、明示的出力をちょうど一つ指定し、ルール名にdyndepを使わなければなりません。| imp-outs...と| imp-ins...の部分は任意です。
-
各build文に、任意でrestatの変数束縛を指定できます。
dyndepファイル内のbuild文は、dyndep束縛でそのdyndepファイルを指定しているNinjaビルドファイル内のbuild文と、一対一に対応しなければなりません。dyndepのbuild文を省略しても、余分なbuild文を指定してもいけません。
Dyndepの例
Fortranモジュール
モジュールfoo.modを提供するFortranソースファイルfoo.f90(モジュールはコンパイルの暗黙の出力)と、そのモジュールを使う別のソースファイルbar.f90(モジュールはコンパイルの暗黙の入力)を考えます。bar.f90をfoo.f90より先にコンパイルせず、foo.modが変わったときにbar.f90を再コンパイルすることを保証するには、いずれかのソースをコンパイルする前に、この暗黙の依存関係を発見しなければなりません。次のように実現できます。
rule f95
command = f95 -o $out -c $in
rule fscan
command = fscan -o $out $in
build foobar.dd: fscan foo.f90 bar.f90
build foo.o: f95 foo.f90 || foobar.dd
dyndep = foobar.dd
build bar.o: f95 bar.f90 || foobar.dd
dyndep = foobar.ddこの例では、順序のみの依存関係によって、いずれのソースのコンパイルよりも前にfoobar.ddが生成されます。仮想的なfscanツールはソースファイルを走査し、それぞれが同名の.oへコンパイルされると想定して、次のような内容をfoobar.ddへ書き込みます。
ninja_dyndep_version = 1
build foo.o | foo.mod: dyndep
build bar.o: dyndep | foo.modNinjaはこのファイルを読み込み、foo.modをfoo.oの暗黙の出力、かつbar.oの暗黙の入力として追加します。これにより、Fortranソースが常に適切な順番でコンパイルされ、必要なときに再コンパイルされます。
tarballの展開
展開したいtarballfoo.tarを考えます。tarballが変わった場合に再度展開するため、展開時刻をfoo.tar.stampファイルへ記録できます。ただし、出力のいずれかが存在しない場合にも、再度展開したいとします。しかし、出力の一覧はtarballの内容に依存するため、Ninjaビルドファイルに明示的に列挙できません。次のように実現できます。
rule untar
command = tar xf $in && touch $out
rule scantar
command = scantar --stamp=$stamp --dd=$out $in
build foo.tar.dd: scantar foo.tar
stamp = foo.tar.stamp
build foo.tar.stamp: untar foo.tar || foo.tar.dd
dyndep = foo.tar.ddこの例では、順序のみの依存関係によって、tarballの展開前にfoo.tar.ddがビルドされます。仮想的なscantarツールは、たとえばtar tfでtarballを読み、次のような内容をfoo.tar.ddへ書き込みます。
ninja_dyndep_version = 1
build foo.tar.stamp | file1.txt file2.txt : dyndep
restat = 1Ninjaはこのファイルを読み込み、file1.txtとfile2.txtをfoo.tar.stampの暗黙の出力として追加し、build文にrestatを指定します。その後のビルドでは、暗黙の出力のいずれかが存在しなければ、tarballが再度展開されます。restat束縛は、暗黙の出力の更新時刻がtarball自体より新しくなくても、Ninjaが許容するように指定します(毎回のビルドで再度展開されることを避けます)。