Sun WorkShop 入門

メークファイルに対する dmake ユーティリティの影響

この項で紹介する方法と例では、dmake により解決できる問題を示しています。必ずしもこれらの方法が最善の解決策であるとは限りませんが、できるだけわかりやすく機能が説明されています。

手順が複雑になるに従って、これを実現するメークファイルも複雑になってきます。したがって、メークファイルを効果的に機能させる上で必要なアプローチもよく理解しておく必要があります。この項では一般的なコード開発の作業を進める上での問題点を取り上げ、dmake による簡単な解決方法について、例を紹介しながら説明します。

メークファイルテンプレートの使用

プロジェクトの最初の段階からメークファイルのテンプレートを使用しておけば、プロジェクトの進行に応じて、このテンプレートをもとにメークファイルをカスタマイズできます。この方法には、次の利点があります。

メークファイルの編集作業が簡便化するにしたがって、プログラムやプロジェクト本来の開発作業に専念できるようになります。

ターゲットの並列構築

大規模なソフトウェア開発プロジェクトは、それぞれ独立した多数のモジュールで構成されるのが一般的です。これらのモジュールは、それぞれ分散させて並列的に構築することができます。dmake ユーティリティでは、ネットワークを介して分散した複数のマシン上で、ターゲットの並列構築を行うことができます。この並列構築により、大規模な開発プロジェクトを構築するのに必要な時間が大幅に省けます。

dmake では、構築の対象となるターゲットを中心に、このターゲットに関連する依存関係を調べ、同期の取れていない依存部分の構築を行います。依存関係の構築とは、依存関係の中に含まれている部分を構築することでもあります。dmake はジョブを分散させた後、対象となるすべてのターゲットを起動します。構築の対象となるすべてのターゲットが終了すると、dmake は構築の対象以外のターゲットを起動します。デフォルトの場合、入れ子構造になった dmake の呼び出しは同時には実行されません。ただし、この設定は変更可能です (詳細については、「並列性を制限する 」を参照してください)。

dmake では複数のターゲットが同時並行で構築されるので、各構築プロセスの出力も同時に生成されます。各コマンドからの出力が混乱しないように、dmake では各構築の出力を個別に収集します。また dmake は、各コマンドを実行する前に、そのコマンドの内容を表示します。実行したコマンドが出力、警告、またはエラーを生成すると、dmake ユーティリティは該当するコマンドの出力全体を表示します。後から開始したコマンドが先行するコマンドよりも先に終了することもあるので、出力の順序が予想と異なる場合があります。

メークファイルの制限

複数のターゲットを同時に構築する場合に、メークファイルには多少の制限が伴います。たとえば、暗黙の順序に依存しているメークファイルは、並列構築を行うと正しく動作しない可能性があります。また同じファイルの修正を行うターゲットがあり、これらファイルが 2 つの異なるターゲットで同時に変更されると、メークファイル中のターゲットが誤動作する場合があります。この項では、こうした問題を取り上げて説明します。

依存関係リスト

ターゲットの並列構築を行う場合、まず依存関係リストが正確であることが重要なポイントになります。たとえば、 2 つの実行可能ファイルが同じオブジェクトファイルを共有しているときに、一方のファイルでしか依存関係が指定されていないと、並列構築を行なった場合はエラーになる可能性があります。次のメークファイルの断片を考えてみます。


all: prog1 prog2 
prog1: prog1.o aux.o 
    $(LINK.c) prog1.o aux.o -o prog1 
prog2: prog2.o 
    $(LINK.c) prog2.o aux.o -o prog2 

直列的に構築を実行する場合は、ターゲット aux.oprog1 の依存対象として作成されるので、prog2 の構築時には更新されています。一方、並列的に構築を実行すると、prog2 のリンクが、aux.o の作成前に設定される可能性があります。この場合は、エラーになります。make.KEEP_STATE 機能で検出できる依存関係も一部ありますが、上のような関係はその対象にはなりません。

依存関係リストの明示的な順序

次に、暗黙の順序にもとづく依存関係がある場合は、問題がさらに複雑になるという例を示します。たとえば、あるシステムのヘッダーをすべて作成してから構築を開始する場合は、すべての要素がこの構築に依存していなければなりません。これに伴いメークファイルがさらに複雑になり、新しいターゲットをメークファイルに追加した場合などにエラーが起きる可能性が高くなります。この場合は、メークファイルの中で特殊な .WAIT ターゲットを指定して、暗黙の依存関係があることを明示することができます。dmake により依存関係リストの中に .WAIT ターゲットが検出されると、これまでの依存関係の処理を一時停止してから、以降の依存関係の処理を実行するようになります。 1 つの依存関係リストに、複数の .WAIT ターゲットを入れることもできます。次に、.WAIT を使用して、まずヘッダーを作成してから、以降の処理を続行する例を示します。


all: hdrs .WAIT libs functions 

.WAIT ターゲットに適用する空の規則をメークファイルに追加すれば、旧バージョンのメークファイルとの互換性も保持されます。

ファイルの同時修正

ターゲットの構築プロセスでは、同時に同じファイルに対して修正作業を行わないように注意する必要があります。この問題は、さまざまな形式で起きる可能性があります。一時ファイルを使用する新しい接尾辞の規則を定義する場合、一時ファイルの名前は、ターゲットごとに異なっていなければなりません。この場合は、$@$* の動的マクロを利用できます。たとえば、ソースファイルをコンパイルする前に .c ファイルに多少の修正を加える場合、.c.o の規則は次のように定義できます。


.c.o:
    awk -f modify.awk $*.c > $*.mod.c
    $(COMPILE.c) $*.mod.c -o $*.o
    $(RM) $*.mod.c

ライブラリの同時更新

並列処理に伴うもう 1 つの問題は、ライブラリを作成するときのデフォルトの規則です。すなわち、ライブラリという固定ファイルが変更される場合があるということです。本来適用されるべきでない .c.a 規則によって、dmake がそれぞれのオブジェクトファイルを生成し、このオブジェクトファイルをアーカイブする可能性があります。dmake が 2 つのオブジェクトファイルを同時にアーカイブすると、並列的に更新が行われてアーカイブファイルが壊れることがあります。


.c.a:
    $(COMPILE.c) -o $% $<
    $(AR) $(ARFLAGS) $@ $%
    $(RM) $%

この場合は、各オブジェクトファイルを作成して、構築が完了した後、すべてのオブジェクトファイルをアーカイブするようにします。正しい接尾辞の規則と対応するライブラリの規則は次のとおりです。


.c.a:
    $(COMPILE.c) -o $% $< lib.a: lib.a($(OBJECTS))
    $(AR) $(ARFLAGS) $(OBJECTS)
    $(RM) $(OBJECTS)

複数のターゲット

ファイルの同時更新の問題は、同じ規則を複数のターゲットに対して定義した場合にも起きます。たとえば、lex(1) を使用してプログラムとヘッダーの両方を作成する yacc(1) プログラムがあります。1 つの規則にもとづいて複数のターゲットファイルを作成する場合は、+ 記号を使用して各ファイルを 1 つのグループとして指定する必要があります。特に、並列構築の場合に、これが重要になります。


y.tab.c y.tab.h: parser.y
    $(YACC.y) parser.y

この規則は、次の 2 つの規則と等価です。


y.tab.c: parser.y
    $(YACC.y) parser.y
y.tab.h: parser.y
    $(YACC.y) parser.y

直列の make ではまず、y.tab.c を生成する最初の規則を使用して構築を行い、次に y.tab.h がすでに最新版になっているため構築の必要がないと判断します。一方、並列構築の場合、dmakeyaccy.tab.c の構築を完了する前に y.tab.h を検索し、y.tab.h が構築の必要があることを知り、最初の yacc を並行して別の yacc を開始します。いずれの yacc の呼び出しも同じファイル (y.tab.cy.tab.h) に書き込まれるので、これらのファイルの内容が不当になる可能性があります。正しい方法は、+ 構文を使用して、両方のターゲットを同じ規則に従って同時に作成するように指示することです。たとえば次のとおりです。


y.tab.c + 
y.tab.h: parser.y
    $(YACC.y) parser.y

並列性を制限する

メークファイルでファイルの衝突が避けられない場合があります。たとえば、C プログラムから文字列を抽出して、共有文字列を生成する xstr(1) コマンドがあります。xstr コマンドでは変更された C プログラムは x.c という固定したファイルに書き込まれ、抽出した文字列は strings という固定ファイルに追加されます。xstr は個々の C ファイルを対象として実行するので、次の .c.o 規則がよく使用されます。


.c.o:
    $(CC) $(CPPFLAGS) -E $*.c | xstr -c - 
    $(CC) $(CFLAGS) $(TARGET_ARCH) -c x.c
    mv x.o $*.o

この場合、各ターゲットを作成するごとに、x.cstrings という同じファイルに書き込みを行うので、dmake ユーティリティは上のルールにもとづいてターゲットを同時に作成することはできません。また使用するファイルを変更することもできません。この場合は、.NO_PARALLEL: という特殊なターゲットを使用して、dmake に各ターゲットを同時には作成しないように指示する必要があります。たとえば、.c.o 規則を使用して作成するオブジェクトを OBJECTS マクロで定義すると、次の指定により dmake は各ターゲットを直列形式で作成するようになります。


.NO_PARALLEL: $(OBJECTS)

大部分のオブジェクトを直列的に作成する場合は、依存関係のない .NO_PARALLEL: ターゲットを入れて、全オブジェクトをデフォルトとして強制的に直列処理するように設定するのが、安全で簡単な方法です。並列的に処理できるターゲットは、次のように .PARALLEL: ターゲットの依存関係として記述できます。


.NO_PARALLEL:
.PARALLEL: $(LIB_OBJECT)

入れ子構造の分散メークの呼び出し

dmake が、さらに別の dmake コマンドを呼び出すターゲットを検出すると、並列ではなく、直列形式でターゲットが作成されます。これにより、 2 つの異なる dmake の呼び出しが、同じディレクトリ中の同じターゲットを作成することはなくなります。このような問題は、 2 つの異なるプログラムを同時に作成して、両方が同じライブラリにアクセスする場合などに起きます。それぞれの dmake の呼び出しで、必ず最新のライブラリを適用するには、dmake を繰り返し呼び出して、このライブラリを作成する必要があります。dmake ユーティリティは、CLI で $(MAKE) マクロが使用されている場合に、入れ子になった呼び出しを認識します。

入れ子のコマンドを使用した場合でも、必ず正しい結果が得られる確信があれば、.PARALLEL: 構文を使用して、並列的に処理することができます。

メークファイルの中に、並列的に実行する入れ子のコマンドが多数含まれている場合は、負荷平均化アルゴリズムのために、ローカルのマシンに割り当てられる構築ジョブが超過する場合があります。この結果、スワップ空間の不足といった別の問題が生じたり、処理負荷が高くなったりすることも考えられます。こうした問題が起きた場合は、入れ子にしたコマンドをすべて直列形式で実行するように変更してください。