この節では、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.o は prog1 の依存ファイルとして構築され、prog2 の構築の際に更新されます。一方、構築が並列モードで行われた場合、aux.o が構築される前に prog2 のリンクが開始されるため、正しい結果が得られないことになります。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
並列処理で発生する可能性があるもう一つの問題は、ライブラリを構築するためのデフォルト規則によって、同じファイル (ライブラリ) が同時に変更されることです。不適切な .c.a 規則を使用すると、dmake は各オブジェクトファイルを構築した後、そのオブジェクトファイルをアーカイブします。dmake が 2 つのオブジェクトファイルを並列処理によってアーカイブしたときに同時に更新が行われると、アーカイブファイルが破壊されることになります。この例を次に示します。
.c.a: $(COMPILE.c) -o $% $< $(AR) $(ARFLAGS) $@ $% $(RM) $%
この問題を回避するためには、すべてのオブジェクトファイルの構築が終了した後、それらのオブジェクトファイルをアーカイブする方法があります。適切な接尾辞の規則とそれに対応するライブラリの指定方法を次に示します。
.c.a: $(COMPILE.c) -o $% $< $(COMPILE.c) -o $% $< lib.a: lib.a($(OBJECTS)) $(AR) $(ARFLAGS) $(OBJECTS) $(RM) $(OBJECTS)
ファイルの同時更新の例は、複数のターゲットに対して同一の規則が定義されている場合にも発生します。例として、lex(1) で使用するためにプログラムとヘッダーの両方を構築する yacc(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 は最新なので次に構築する必要はないと判断されます。並列モードで構築を行うと、dmake は yacc によって y.tab.c が構築される前に y.tab.h をチェックするため、y.tab.h を構築する必要があると判断し、最初の yacc の処理と並行して、別の yacc を開始します。このように 2 つの yacc が、同じファイル (y.tab.c および y.tab.h) に書き込みを行うため、これらのファイルは破壊され、正しい結果が得られません。この規則を正しく指定するには、 + を使って 2 つのターゲットが同じ規則により同時に構築されることを示します。正しい規則の例を次に示します。
y.tab.c + y.tab.h: parser.y $(YACC.y) parser.y
1 つのメークファイルの中でファイルの衝突が避けられない場合があります。次の例で、xstr(1) は C プログラムから文字列を抽出することによって文字列を共有しています。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
上記の規則を使用した場合、dmake ユーティリティでは同時に複数のターゲットを構築することはできません。これは、各ターゲットを構築するたびに、同じ x.c ファイルと strings ファイルを書き換えることになり、しかも使用するファイルを変更することはできないためです。このような状況では、特殊ターゲットの .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 を再帰的に呼び出してライブラリを構築することができます。コマンド行で $(MAKE) マクロを指定すると、dmake ユーティリティが入れ子呼び出しを認識できます。
入れ子にしたコマンドが衝突を起こさないとわかっている場合は、.PARALLEL: を使用して、強制的に並列構築が行われるようにすることができます。
メークファイルの中に、並行して実行される入れ子のコマンドが多数含まれている場合、負荷平均化アルゴリズムによって、強制的にローカルマシンへの構築処理が過剰に割り当てられることがあります。これによって、負荷が大きくなったり、スワップ領域が不足するなどの問題を引き起こすことがあります。こうした問題が発生した場合は、入れ子にしたコマンドを逐次的に実行するようにしてください。