GNU make は、統合開発環境(IDE)やコンパイラのツールチェインなど、より大きなツール群の中の一部品として使われることがよくあります。make の役割は、コマンドを起動し、それが成功したかどうかを判定することです。この役割を果たすだけなら、特別な連携の仕組みは必要ありません。しかし、make をシステムの他の部分ともっと緊密に結びつけたほうが便利な場合があります。これには、上位のもの(make を起動する側のツール)との連携と、下位のもの(make が起動する側のツール)との連携の両方が含まれます。
GNU make には、複数のレシピを並列に実行する能力(並列実行を参照してください)があり、さらに make の再帰的な呼び出しをまたいでも並列ジョブの総数に上限を設ける能力(サブmakeへオプションを伝えるを参照してください)があります。make が起動するツールの中には、複数のスレッドや複数のプロセスを使って、それ自身も複数の処理を並列に実行できるものがあります。こうしたツールを拡張して GNU make のジョブ管理機構に参加させれば、システム上で稼働中のスレッド/プロセスの総数が、GNU make に与えられたスロットの最大数を超えないように保つことができます。
GNU make は、再帰的な呼び出しをまたいで稼働中のジョブ数を制御するために「ジョブサーバ(jobserver)」と呼ばれる方式を使います。ジョブサーバの実際の実装はオペレーティングシステムによって異なりますが、いくつかの基本的な点は常に共通しています。
第一に、make はジョブサーバにアクセスするために必要な情報を、環境変数 MAKEFLAGS を通じて子プロセスに渡します。ジョブサーバプロトコルに参加したいツールは、この環境変数を解析し、--jobserver-auth= で始まる語を見つける必要があります。このオプションの値が、ジョブサーバとどう通信すればよいかを表しています。この値の解釈の仕方は、以下の各節で説明します。
なお、MAKEFLAGS 変数には --jobserver-auth= オプションが複数含まれていることがある、という点に注意してください。意味を持つのは最後のインスタンスだけです。
第二に、make が起動するコマンドには、起動前にあらかじめ暗黙のジョブスロットが1つ予約されています。ジョブサーバプロトコルに参加したいツールは、ジョブサーバにまったく問い合わせることなく、常に1つのジョブを実行できるものと考えてかまいません。
最後に、ジョブサーバプロトコルに参加するツールは、終了する前に——たとえエラーが起きた場合でも——ジョブサーバから取得したスロットの数を正確にジョブサーバへ返すことが極めて重要です。先ほどの暗黙のジョブスロットは、ジョブサーバへ返してはいけないという点を忘れないでください! スロットを返す数が少なすぎると、その分のスロットはビルド処理の残りの間ずっと失われたままになります。逆にスロットを返す数が多すぎると、余分なスロットが利用可能になってしまいます。トップレベルの make コマンドは、ジョブサーバ内で利用可能なスロット数が正しくないことを検出すると、ビルドの最後にエラーメッセージを表示します。
例として、マルチスレッド動作に対応したリンカを実装している場合を考えてみましょう。あなたはこのリンカを拡張して、GNU make から起動されたときにはジョブサーバプロトコルに参加し、リンク中に使うスレッド数を制御できるようにしたいとします。まず、MAKEFLAGS 環境変数が設定されているかどうかを判定するようにリンカを改修する必要があります。次に、その変数の値を解析して、ジョブサーバが利用可能かどうか、そしてどうアクセスすればよいかを判断します。利用可能であれば、そこにアクセスしてジョブスロットを取得し、自分のツールがどれだけの並列度を使えるかを制御できます。処理が終わったら、ツールはそれらのジョブスロットをジョブサーバへ返さなければなりません。
POSIX システムでは、ジョブサーバは2通りのいずれかの方法で実装されます。名前付きパイプ(named pipe)をサポートするシステムでは、GNU make は名前付きパイプを作成し、それをジョブサーバとして使います。この場合、auth オプションは --jobserver-auth=fifo:PATH という形になります。ここで「PATH」は名前付きパイプのパス名です。ジョブサーバにアクセスするには、この名前付きパイプのパスを開き、以下で説明するとおりに読み書きします。
システムが名前付きパイプをサポートしていない場合、あるいはユーザが --jobserver-style オプションを与えて「pipe」を指定した場合には、ジョブサーバは単純な UNIX パイプとして実装されます。この場合、auth オプションは --jobserver-auth=R,W という形になります。ここで「R」と「W」はファイルディスクリプタを表す非負の整数で、「R」が読み込み用、「W」が書き込み用のファイルディスクリプタです。これらのファイルディスクリプタのいずれか一方または両方が負の値であれば、それはこのプロセスに対してジョブサーバが無効になっていることを意味します。
単純なパイプを使う場合、ジョブサーバにアクセスできるのは、make が make 自身の再帰的な呼び出しだと認識しているコマンド行だけです(MAKE変数の働きを参照してください)。makefile を書くときには、そのコマンドを再帰的であると確実にマーク付けしておかなければなりません(もっとも一般的なやり方は、コマンド行の先頭に + という印を付けることです。makeの再帰的な使い方を参照してください)。なお、ジョブサーバのパイプの読み込み側は「ブロッキング(blocking)」モードに設定されています。これは変更しないでください。
ジョブサーバのどちらの実装でも、パイプには利用可能なジョブ1つにつき1個の1文字トークンがあらかじめ入れられています。スロットを追加で1つ取得するには、ジョブサーバから1文字を読み込まなければなりません。スロットを1つ解放するには、ジョブサーバへ1文字を書き戻さなければなりません。
ジョブスロットを解放するときには、読み込んだのと同じ文字を書き戻すことが重要です。トークンがすべて同じ文字だとは思い込まないでください。文字が異なれば、GNU make にとって異なる意味を持つことがあります。順序は重要ではありません。そもそも make は、どんな順序でジョブが完了するのかをまったく知らないからです。
実装を堅牢にするために、考慮しておくべきさまざまなエラー条件があります:
--jobserver-auth 文字列の形式を認識できない場合は、ジョブサーバが別のスタイルを使っていて接続できないものとみなすべきです。
--jobserver-auth オプションは単純なパイプを指しているが、指定されたファイルディスクリプタが閉じられていると判断した場合、それは呼び出し元の make プロセスがあなたのツールを再帰的な make の呼び出しだとは見なさなかったことを意味します(たとえば、コマンド行の先頭に + 文字が付いていなかった、など)。この状況はユーザに知らせるべきです。
SIGINT)など外部からの影響も含まれます。この書き戻しを管理するために、シグナルハンドラを設置するとよいでしょう。
MAKEFLAGS 変数の最初の語を調べて、その中に n という文字がないかを確認することもできます。この文字が存在する場合、make は「-n」オプション付きで起動されているので、あなたのツールは何の処理も行わずに停止したほうがよいかもしれません。
Windows システムでは、ジョブサーバは名前付きセマフォ(named semaphore)として実装されます。セマフォは、利用可能なスロット数に等しい初期カウントで設定されます。スロットを取得するには、(タイムアウトの有無にかかわらず)セマフォを待機(wait)しなければなりません。スロットを解放するには、セマフォを解放(release)します。
セマフォにアクセスするには、MAKEFLAGS 変数を解析して、引数文字列 --jobserver-auth=NAME を探さなければなりません。ここで「NAME」は名前付きセマフォの名前です。この名前を OpenSemaphore に渡して、セマフォへのハンドルを作成します。
--jobserver-style に指定できる唯一の有効なスタイルは「sem」です。
実装を堅牢にするために、考慮しておくべきさまざまなエラー条件があります:
SIGINT)など外部からの影響も含まれます。この書き戻しを管理するために、シグナルハンドラを設置するとよいでしょう。
通常、GNU make はすべてのコマンドを、make 自身が起動されたときと同じ標準出力・標準エラー出力にアクセスできる状態で起動します。多くのツールは、出力先が端末(terminal)なのかそうでないのかを検出し、その情報をもとに出力スタイルを変えます。たとえば、出力が端末に向かう場合、ツールは色を設定する制御文字を加えたり、カーソルの位置を変えたりすることがあります。出力が端末に向かわない場合は、ログファイルなどを壊さないように、こうした特殊な制御文字を出力しません。
--output-sync オプション(並列実行時の出力を参照してください)を使うと、この端末の検出が無効になります。出力同期が有効なとき、GNU make はすべてのコマンド出力をいったんファイルに書き出すように手配します。これは、他のコマンドの出力に邪魔されることなく、出力をひとまとまりのブロックとして書き出せるようにするためです。その結果、make が起動したすべてのツールは、自分の出力が端末に表示されないものと思い込んでしまいます。たとえ実際には端末に表示される(コマンドの完了後に make がそこに表示する)場合でもです。
出力が端末に表示されるかどうかを判定したいツールを手助けするために、GNU make はコマンドを起動する前に環境変数 MAKE_TERMOUT と MAKE_TERMERR を設定します。標準出力(または標準エラー出力)が端末に表示されるかどうかを判定したいツールは、これらの環境変数が存在し、かつ空でない値を持っているかどうかを確認すればよいのです。もしそうであれば、ツールは出力が(最終的には)端末に表示されるものとみなすことができます。これらの変数が設定されていないか、空の値であれば、ツールは出力が端末に向かうかどうかを判定する通常の手段にフォールバックすべきです。
これらの変数の内容を解析すれば、出力の表示に使われる端末の種類を判別することもできます。
同様に、make を起動して、その出力を取り込み、最終的に端末(あるいは端末の制御文字を解釈できる何らかの表示装置)に表示したいと考える環境は、make を起動する前にこれらの変数を設定しておくことができます。GNU make は、起動時にこれらの環境変数がすでに存在していれば、それらを変更しません。