これまでの節では、簡単なプログラムをコンパイルして簡単なライブラリを構築する際に make を利用する方法を説明しました。この節では、複雑なプログラムおよびライブラリを管理する make のより高度な機能について説明します。
マクロ定義は、メークファイルの任意の行に記述できます。マクロは、長いターゲットリストや式を短縮するために、あるいは繰り返し記述する必要のある長い文字列の代替として使用できます。
また、マクロを使用して、オブジェクトファイルのリストをソースファイルのリストから取得できます。マクロ名は、メークファイルの読み取り時に割り当てられます。マクロ参照の値は、最後に割り当てられた値によって決定されます。
マクロの評価は、実際には上述よりも複雑に行われます。「暗黙の規則と動的なマクロ」を参照してください。
条件付きマクロおよび動的マクロを除き、make はマクロの値をマクロの定義順に割り当てます。
マクロ参照は、他のマクロの参照箇所に組み込むことができます (旧バージョンの make では使用できません)。
$(CPPFLAGS$(TARGET_ARCH))
代入文の += は、指定された文字列をマクロの既存の値の後に追加します。
この場合は、マクロは内側から外側に展開されます。以下の定義例では、make は Sun-4 システム用の正しいシンボルを定義します。
CPPFLAGS-sun4 = -DSUN4 CPPFLAGS += $(CPPFLAGS-$(TARGET_ARCH))
make には、参照されるマクロの値に含まれる特定の接尾辞を置換する機能があります。慣例として、接尾辞はドット (.) で始まりますが、任意の文字列を接尾辞として指定することもできます。
$(macro:old-suffix=new-suffix)
上記の形式は、接尾辞を置換するマクロ参照の書式です。このような参照を使用することによって、以下のようにオブジェクトファイルのリストをソースファイルのリストで表わすことができます。
OBJECTS= $(SOURCES:.c=.o)
この例では、make は値に含まれる接尾辞 .c をすべて .o に置換します。指定された接尾辞が付いていない語に対しては置換は適用されません。
SOURCES= main.c data.c moon OBJECTS= $(SOURCES:.c=.o) all: @echo $(OBJECTS)
例えば、上記のメークファイルで make を実行すると次のような結果になります。
$ make main.o data.o moon
C プログラムをより容易にデバッグおよび管理するには、lint ツールを使用します。lint は、他のアーキテクチャへの移植性がない C の構造体の検査も行います。
lint は、移植性のある C プログラムを記述する際に非常に便利です。C プログラムの検査を行う lint は、発見および追跡するのが難しいバグを防ぐためのツールです。初期化されていないポインタ、関数呼び出しにおける引数の数の不一致、移植不可能な C の構造体の使用、などのバグがないかどうかを検査します。clean と同様に、lint はターゲットの慣用名です。C プログラムを構築するメークファイル中に、lint を含めることをお勧めします。lint は、cpp および lint の最初の (構文解析) 処理過程で処理された出力ファイルを生成します。出力ファイルは、接尾辞が .ln となります。また、接尾辞の置換によって、ソースファイルのリストから lint の出力ファイルを生成することもできます (旧バージョンの lint では不可能な場合があります)。
LINTFILES= $(SOURCES:.c=.ln)
lint のターゲットエントリは、以下のようになります。
lint: $(LINTFILES) $(LINT.c) $(LINTFILES) $(LINTFILES): $(LINT.c) $@ -i
各 .ln ファイルは、対応する .c ファイルから構築するという暗黙の規則があるため、.ln ファイルに対するターゲットエントリは必要ありません。ソースが変更されると、以下のように make を実行するときに .ln ファイルが更新されます。
make lint
LINT.c
という定義済みマクロに lint のオプション指定として LINTFLAGS
マクロへの参照が含まれていますが、通常は特に指定しなくてよいでしょう。lint では cpp を使用するため、コンパイルのプリプロセッサオプション (-I など) に対して、通常は CFLAGS ではなく CPPFLAGS を使用してください。また、LINT.c
マクロには CFLAGS
への参照は含まれていません。
make clean を実行すると、このターゲットによって生成された .ln ファイルが削除されるようにしたいと考えられます。clean ターゲットには、そのようなマクロ参照を簡単に追加することができます。
次の例は、画面上でのカーソル動作を制御する curses および termlib というライブラリパッケージを使用したプログラムをコンパイルするメークファイルです。
表 4-11 システムが提供するライブラリを使用した C プログラム用のメークファイル
リンカーは、未定義のシンボルを検出するとそれを解決するため、通常は、リンクするファイルのリストの最後に、ライブラリの参照を記述してください。
このメークファイルの実行結果は、以下のとおりです。
$ make cc -O -c main.c cc -O -c data.c cc -O -o functions main.o data.o -lcurses -ltermlib
デバッグ用およびプロファイル用のプログラムは同じソースコードを使用して生成しますが、構築の際に異なる C コンパイラオプションを使用します。デバッグ用のオブジェクトコードを生成するには、cc の -g オプションを使用します。プロファイル用には、-O および -pg という cc オプションを使用します。
コンパイル手順は同じなので、make のコマンド行で CFLAGS
の定義を指定できます。このコマンド行での定義は、メークファイルでの定義を無効にします。また、.KEEP_STATE により、変更の影響を受けるすべてのコマンド行が実行されます。以下に例を示します。
$ make "CFLAGS= -O -pg" cc -O -pg -c main.c cc -O -pg -c data.c cc -O -pg -o functions main.o data.o -lcurses -ltermlib
これらのオプションを暗記したり、このような複雑なコマンドを入力するのは煩雑です。その代わりに、これらの情報はメークファイルに記述できます。デバッグ用またはプロファイル用のコードの生成方法をメークファイルに記述し、make に伝える必要があります。1 つの方法として、debug および profile という名前の 2 つのターゲットエントリと、コマンド行へのそれぞれのコンパイルオプションを、メークファイルに追加します。
よりよい方法として、開始ターゲットに応じて CFLAGS
の定義を変更する規則を指定する、debug および profile というターゲットエントリを追加します。次に、各ターゲットが既存のターゲットに依存するようにすることによって、make は既存のターゲットの規則と、指定されたオプションを使用することができます。
以下に例を示します。
make "CFLAGS= -g"
前述のようにメークファイルを記述すると、上記のように make を実行する代わりに、以下のようにしてデバッグ用のコードをコンパイルすることができるようになります。
make debug
ここで、ターゲット (およびその依存関係) ごとに異なる方法でマクロを定義することを、どのように make に指示するかが問題になります。
条件付きマクロ定義は、以下のような形式で記述します。
target-list := macro = value
make が target-list に指定された名前のターゲットおよびその依存関係を処理する際に、指定されたマクロに指定された値を割り当てます。
target-list 中のそれぞれの語は、% のパターンを 1 つずつ含めることができます。定義が適用されるターゲットを make が特定する必要があるため、条件付きマクロ定義を使用してターゲット名を変更することはできません。
以下の行は、デバッグ用およびプロファイル用の各プログラムコードを処理するための適切な値を CFLAGS
に割り当てます。
debug := CFLAGS= -g profile := CFLAGS= -pg -O
条件付きマクロへの参照を依存関係リスト中で使用する際には、遅延参照を使用する (先頭に $ を 1 つ追加する) 必要があります 。遅延参照を使用しないと、正しい値が割り当てられる前に make が参照を展開してしまいます。make は、このような正しくない (可能性のある) 参照を検出すると警告を表示します。
以下のメークファイルは、指定したターゲットに応じて、最適化された C プログラム、デバッグ用の C プログラム、プロファイル用の C プログラムのいずれかの形式の C プログラム (デフォルトでは最適化されたプログラム) をコンパイルします。コマンドの依存関係が検査されるので、上記 3 つのうちで形式を切り換えると、プログラムおよびそのオブジェクトファイルが再コンパイルされます。
表 4-12 デバッグ形式またはプロファイル形式の C プログラムを生成するメークファイル
最初のターゲットエントリは、all によって 3 つのターゲットを指定しています。
通常最終的な完成プログラムには、デバッグ形式およびプロファイル形式のコードが含まれないようにします。
慣例として、メークファイルの最初のターゲットとして、代替の開始ターゲット (またはターゲットのリストを処理するターゲット) とともに all を記述します。all の依存関係は、種類を問わず、最終的に構築されるすべてのターゲットになります。この場合は、最終的には最適化されたプログラムが構築されます。ターゲットエントリは、debug および profile が functions ($(PROGRAM) の値) に依存することも示しています。
次の 2 行は、CFLAGS
の条件付きマクロ定義です。
次に、functions のターゲットエントリが記述されています。debug が functions に依存している場合は、-g オプションを使用してコンパイルされます。
次の例では、同様の手法を C のオブジェクトライブラリの管理に使用しています。
表 4-13 デバッグ形式またはプロファイル形式の C ライブラリを生成するメークファイル
前述の 2 つの例は、開発、デバッグ、プロファイルを別々に実行する場合には適しています。しかし、形式を切り替えるごとにすべてのオブジェクトファイルが再コンパイルされるので、コンパイル時間が長くなるという欠点があります。以下の 2 つの例は、3 つの形式すべてを独立した構成要素として別々に管理する方法を説明しています。
3 つの形式のオブジェクトファイルが同一のディレクトリにあることによる混乱を避けるため、デバッグ用およびプロファイル用のオブジェクトファイルと実行可能ファイルをサブディレクトリに保存できます。ただし、オブジェクトファイルのリストの各エントリに、サブディレクトリ名を接頭辞として追加する必要があります。
パターン置換マクロ参照は、形式および機能が接尾辞置換の参照と同様です。パターン置換参照を使用して、接頭辞、接尾辞のいずれかまたは両方を、マクロの値中で一致する語に追加または置換できます。
パターン置換マクロ参照は、パターンマッチングの規則と同様に、旧バージョンの make で使用することはできません。
パターン置換参照は、以下の形式で記述します。
$(macro:p%s =np%ns)
ここで、p は置換対象の既存の接頭辞 (ある場合)、s は置換対象の既存の接尾辞 (ある場合)、np および ns はそれぞれ新しい接頭辞および接尾辞、% はワイルドカードです。パターン置換は、値 p%s に一致するすべての語に適用されます。
SOURCES= old_main.c old_data.c moon OBJECTS= $(SOURCES:old_%.c=new_%.o) all: @echo $(OBJECTS)
この例は、以下のような結果になります。
$ make new_main.o new_data.o moon
= 記号の右側 (置換後の語) では、ワイルドカードの % を任意の数だけ必要に応じて使用できます。以下に例を示します。
... OBJECTS= $(SOURCES:old_%.c=%/%.o)
この置換は、以下のような結果になります。
main/main.o data/data.o moon
ただし、パターン置換マクロ参照は、パターンマッチングの規則を指定しているターゲットエントリの依存関係を示す行では使用しないでください。使用した場合は、マクロとターゲット (または依存関係) のどちらにワイルドカードが適用されるかを make が特定できないため、衝突が生じます。
OBJECT= .o x: x.Z: @echo correct %: %$(OBJECT:%o=%Z)
このメークファイルは、make が x.Z から x を構築するために記述したものです。しかし、依存関係の行に複数含まれている % のうちどれをパターンマッチングの規則で使用するかを make が特定できないため、パターンマッチングの規則は認識されません。
次に示すのは、複数の形式が別々に管理されている C プログラム用のメークファイルの例です。まず .INIT という特殊ターゲットが、debug_dir および profile_dir というサブディレクトリを (まだ作成されていなければ) 作成します。これらのサブディレクトリには、デバッグ用およびプロファイル用のオブジェクトファイルと実行可能ファイルが含まれます。
make は、メークファイルが読み込まれた後に .INIT ターゲットの規則を実行します。
実行可能ファイルは、VARIANTS.o
マクロで指定されているオブジェクトファイルに依存します。このマクロにはデフォルトでは OBJECTS の値が指定され、後に条件付きマクロ定義によって値が再度割り当てられます。このとき、debug_dir/ または profile_dir/ の接頭辞が追加されます。サブディレクトリ内の実行可能ファイルは、同じサブディレクトリ内に構築されるオブジェクトファイルに依存します。
次に、両方のサブディレクトリに含まれているオブジェクトファイルが作業中のディレクトリにあるソース (.c) ファイルに依存するように、パターンマッチングの規則が追加されます。これは、1 つのソースファイル群から 3 つの形式すべてを構築し管理するために必要です。
最後に、サブディレクトリの debug_dir と profile_dir は一時的に作成されたものであるため、clean ターゲットが更新されてこれらのサブディレクトリとその内容が再帰的に削除されます。これは、各形式用のサブディレクトリは一時的なものであるため、派生ファイルはそのソースと同一のディレクトリ内に構築するという慣例に従っています。
表 4-14 デバッグ用およびプロファイル用のプログラムを別々に扱うためのメークファイル
複数の形式があるライブラリ用のメークファイルも、同様の方法で変更します。
# 複数の形式のライブラリを別々に扱うためのメークファイル CFLAGS= -O SOURCES= main.c rest.c LIBRARY= lib.a LSOURCES= fnc.c OBJECTS= $(SOURCES:%.c=$(VARIANT)/%.o) VLIBRARY= $(LIBRARY:%.a=$(VARIANT)/%.a) LOBJECTS= $(LSOURCES:%.c=$(VARIANT)/%.o) VARIANT= . program profile debug: $$(OBJECTS) $$(VLIBRARY) $(LINK.c) -o $(VARIANT)/$@ $< lib.a debug_dir/lib.a profile_dir/lib.a: $$(LOBJECTS) ar rv $@ $? $$(VLIBRARY)($$(VARIANT)%.o): $$(VARIANT)%.o @true profile := VARIANT = profile_dir profile := CFLAGS = -O -pg debug := VARIANT = debug_dir debug := CFLAGS = -g .KEEP_STATE: profile_dir debug_dir: test -d $@ || mkdir $@ $$(VARIANT)/%.o: %.c $(COMPILE.c) $< -o $@
複数の形式を管理するこの手法は、便利ですがやや複雑です。説明を簡潔にするため、この手法は以下の例では省略しています。
ヘッダーのインクルードディレクトリを管理するためのメークファイルは、非常に簡単です。ヘッダーファイルはテキストファイルなので、記述する必要があるのは all というターゲットだけです。この all ターゲットで、ヘッダーを依存関係として指定します。その他の処理は、SCCS の自動取り出しによって行われます。ヘッダーのリストの代わりにマクロを使用すると、他のターゲットエントリで同一のリストを使用できます。
# インクルードディレクトリを管理するためのメークファイル FILES.h= calc.h map.h draw.h all: $(FILES.h) clean: rm -f $(FILES.h)
ユーザー定義のライブラリパッケージを作成する際には、各ライブラリを、ライブラリのヘッダーおよびライブラリを使用するプログラムとは別の構成要素として扱ってください。プログラム、ライブラリ、ヘッダーをそれぞれ別のディレクトリに置くと、各モジュール用のメークファイルの作成が簡単になります。また、ソフトウェアプロジェクトの構造が明確になります。
make を実行した後に、ファイルシステム上のいろいろな場所にファイルが生成されないようにしてください。
メークファイルは、作業中のディレクトリまたは一時的なサブディレクトリ内のファイルだけを構築するように記述する必要があります。make を使用して、何らかの理由で特定のファイルシステム上のディレクトリに意図的にファイルをインストールする場合を除き、他のディレクトリにファイルを作成するメークファイルは作成しない方がよいでしょう。
他のディレクトリにあるライブラリに依存するプログラムを構築する場合には、メークファイル中で修正が必要な点があります。これまでの例では、すべての必要なファイルは、同一のディレクトリ中か、または基本的には変更されない標準ディレクトリ中にあります。ただし、開発中のプロジェクトの一部であるユーザー定義のライブラリについては、場所が変更されることも考えられます。
これらのライブラリは自動的に構築されない (隠れた依存関係の検査に相当するものがライブラリにはない) ため、ライブラリのターゲットエントリを指定する必要があります。また、リンクするライブラリが最新のものであることを確認する必要があります。
また、ローカルディレクトリ内でのみファイルを管理するようにメークファイルを記述する必要があります。さらに、メークファイル中には、別のメークファイルにある内容と重複する情報を記述しないようにする必要があります。
以上の問題を解決するには、ライブラリがあるディレクトリで make コマンドを入れ子にして実行し、(そのディレクトリにあるメークファイル中のターゲットエントリに従って) ライブラリを構築します。
デフォルトでは "make" という値に設定されている MAKE
マクロは、make コマンドの -n オプションを無効にします。つまり MAKE
マクロを参照しているコマンドは、-n オプションが指定されている場合でも実行されます。ただし MAKE
マクロは make コマンドを呼び出すためにのみ使用され、このマクロによって呼び出された make は、特殊マクロ MAKEFLAGS
から -n オプションを継承します。入れ子 (階層構造) になった make はそれぞれ MAKEFLAGS
マクロによって -n オプションが指定されていることを認識していきます。このため、-n オプションを使用することで、入れ子になった make の動作を実際に実行せずに確認することができます。
# 他のディレクトリに生成されるターゲット用の最初のエントリ ../lib/libpkg.a: cd ../lib ; $(MAKE) libpkg.a
ライブラリは、現在のディレクトリからの相対パス名で指定します。プロジェクトが新しいルートディレクトリまたはマシンに移動された場合に、新しいルートディレクトリに対するディレクトリ構造がそのまま同じであれば、すべてのターゲットエントリが正しいファイルを示します。
入れ子にした make のコマンド行では、定義済みマクロ MAKE
の場合と同様に、動的マクロの修飾子の F および D が便利です。処理されるターゲットがパス名で指定されている場合は、$(@F) はファイル名部分、$(@D) はディレクトリ部分をそれぞれ示します。ターゲット名に / という文字が含まれていない場合は、$(@D) の値としてドット (.) が値として割り当てられます。
ターゲットエントリは、次のように書き換えることができます。
# 2 番目のエントリ ../lib/libpkg.a: cd $(@D); $(MAKE) $(@F)
このターゲットには依存関係がないため、../lib/libpkg.a という名前のファイルがないときにだけこのターゲットが実行されます。ファイルが .PRECIOUS により保護されたライブラリアーカイブである場合は、../lib/libpkg.a ファイルがないということはほとんどありません。make はそのファイルの依存関係を認識する必要はないため、認識しません。ファイルを構築するかどうか、およびその構築方法は、入れ子にした呼び出しによって決定されます。
つまり、ファイルシステム内にファイルがあっても、そのファイルが最新でない場合もあります。したがって、ファイルがあるかどうかに関わらず、そのファイルを規則が空白の (および既存のファイルがない) 他のターゲットに依存させることによって、入れ子にした make コマンドを強制的に実行する必要があります。
表 4-15 入れ子にした make コマンド用のターゲットエントリ
# 入れ子にした make コマンド用 # ターゲットエントリ ../lib/libpkg.a: FORCE cd $(@D); $(MAKE) $(@F) FORCE: |
この方法により、make は、正しいディレクトリ ../lib に変更し、そのディレクトリにあるメークファイルに記述された命令に従って、必要であれば libpkg.a を構築します。入れ子にした make の実行結果は次のようになります。
$ make ../lib/libpkg.a cd ../lib; make libpkg.a make libpkg.a `libpkg.a' is up to date.
以下のメークファイルは、入れ子にした make コマンドを使用して、プログラムが依存するユーザー定義のライブラリを処理します。
表 4-16 ユーザー定義のライブラリを使用した C プログラム用のメークファイル
../lib/libpkg.a が最新である場合は、このメークファイルを使用する make の実行結果は以下のようになります。
$ make cc -O -c main.c cc -O -c data.c cd ../lib; make libpkg.a `libpkg.a' is up to date. cc -O -o functions main.o data.o ../lib/libpkg.a -lcurses -l termlib
MAKEFLAGS
マクロ
MAKE
マクロと同様に、MAKEFLAGS
も特殊マクロです。
MAKEFLAGS
をメークファイル中に定義しないでください。
MAKEFLAGS
には、make コマンド用のフラグ (1 文字のオプション) が含まれています。他の FLAGS マクロとは異なり、MAKEFLAGS
の値は、フラグの冒頭に付いている - (ハイフン) を除いて連結したものになります。たとえば、eiknp という文字列は、MAKEFLAGS
の値として認識されますが、-f x.mk や macro=value は値として認識されません。
MAKEFLAGS
という環境変数が設定されている場合は、make は、コマンド行で指定されたフラグと、MAKEFLAGS
に含まれるフラグを組み合わせて実行されます。
MAKEFLAGS
の値は、環境変数で設定されているかどうかに関係なく常にエクスポートされ、MAKEFLAGS
に含まれるオプションは、($(MAKE)、make、/usr/bin/make のうちどれによって呼び出されたかに関係なく) 入れ子にした make コマンドに渡されます。これにより、親の make が呼び出された際のオプションが、入れ子にした make コマンドに渡されます。
MAKEFLAGS
を除いて、make は環境変数をインポートし、定義済みのマクロと同様に扱います。次に、make は呼び出したコマンド (入れ子になった make コマンドを含む) にそれらの環境変数およびその値を渡します。
環境変数 SHELL
は、このバージョンの make にはインポートおよびエクスポートされません。
マクロは、メークファイルと同様に、コマンド行の引数としても指定できます。このため、マクロが複数の箇所で定義されている際に名前が衝突することがあります。make には、このような衝突を回避するための非常に複雑な優先順位の規則があります。
まず、条件付きマクロの定義は、定義されているターゲット (およびその依存関係) 内で常に有効です。
マクロ定義を引数として make を呼び出すと、その定義はメークファイル内のマクロ定義または環境変数からインポートされたマクロ定義よりも優先されます (ただし、入れ子にした make コマンドではこの優先順位とは異なる場合があります)。それ以外の場合は、メークファイルでマクロを定義 (または再定義) した場合は、最新の定義が適用されます。通常は、最新の定義が環境変数の定義よりも優先されます。
最後に、マクロがデフォルトのファイルだけで定義されている場合は、その値が使用されます。
入れ子にした make コマンドでは、通常はメークファイルでの定義が環境変数よりも優先されますが、これは定義がメークファイルに記述されている場合のみ該当します。対応する環境変数はこれとは無関係に伝達されます。
コマンド行での定義は、その定義を指定した make の実行でのみ、環境変数とメークファイルの定義を無効にします。コマンド行で指定した値は、入れ子にした make コマンドにも渡されますが、入れ子にした make コマンドでの定義と、入れ子にした make コマンドによりインポートされた環境変数によって無効になります。
-e オプションの動作はさらに一貫しています。環境変数は、メークファイルでのマクロ定義を無効にします。コマンド行での定義は、メークファイルおよび 環境変数での定義よりも常に優先して使用されます。ただし、-e を使用すると、メークファイルに含まれていない情報により構築の成否が左右される可能性があります。
このような複雑さを避けるために、特定の値を make コマンドの階層全体に渡す際には、環境変数を (C シェルで) 設定して、サブシェルで make -e を実行してください。
% (unsetenv MAKEFLAGS LDFLAGS; setenv CFLAGS -g; make -e)
以下のメークファイルを使用して、さまざまな場合をテストすることができます。
# top.mk MACRO= "Correct but unexpected." top: @echo "------------------------------ top" echo $(MACRO) @echo "------------------------------" $(MAKE) -f nested.mk @echo "------------------------------ clean" clean: rm nested
# nested.mk MACRO=nested nested: @echo "------------------------------ nested" touch nested echo $(MACRO) $(MAKE) -f top.mk $(MAKE) -f top.mk clean
以下に、マクロの割り当て順序の一覧表を示します。
表 4-17 マクロの割り当て順序の一覧表
-e なし |
-e を使用 |
---|---|
最上位の make コマンド |
|
条件付き定義 |
条件付き定義 |
make のコマンド行 |
make のコマンド行 |
最新のメークファイルの定義 |
環境変数の値 |
環境変数の値 |
最新のメークファイルの定義 |
定義済みの値 (ある場合) |
定義済みの値 (ある場合) |
入れ子にした make コマンド |
|
条件付き定義 |
条件付き定義 |
make のコマンド行 |
make のコマンド行 |
最新のメークファイルの定義 |
親の make コマンド行 |
環境変数 |
環境変数 |
定義済みの値 (ある場合) |
最新のメークファイルの定義 |
親の make コマンド行 |
定義済みの値 (ある場合) |
次の例のメークファイルは、アセンブリ言語のルーチンとリンクした C プログラムを管理します。アセンブリのソースファイルには、cpp プリプロセッサ指令を含むものと含まないものの 2 種類あります。
慣例として、プリプロセッサ指令を含まないアセンブリのソースファイルには .s という接尾辞が付きます。プリプロセッサ処理が必要なアセンブリのソースには、.S という接尾辞が付きます。
ASFLAGS は、.s.o および .S.o の暗黙の規則に関するオプションを渡します。
アセンブリのソースは、C ソースをコンパイルするのと同様の方法でアセンブルされてオブジェクトファイルを形成します。そのオブジェクトファイルは、C プログラムにリンクできます。make には、.s および .S のファイルをオブジェクトファイルに変換するための暗黙の規則があるため、アセンブリのルーチンを持つ C プログラムのターゲットエントリでは、オブジェクトファイルのリンク方法を指定するだけです。アセンブラによって生成されたオブジェクトファイルをリンクするには、cc コマンドを使用できます。
表 4-18 アセンブルソースファイルから C プログラムを生成するメークファイル
CFLAGS= -O ASFLAGS= -O .KEEP_STATE: driver: c_driver.o s_routines.o S_routines.o cc -o driver c_driver.o s_routines.o S_routines.o |
.S ファイルは、cc コマンドを使用して処理されます。cc コマンドは、C のプリプロセッサ cpp およびアセンブラを呼び出します。
lex および yacc は、C ソースファイルを出力します。lex および yacc のソースファイルは、接尾辞がそれぞれ .l、.y になります。lex および yacc のソースファイルを別々にコンパイルする場合のコンパイル処理は、C ソースだけからプログラムを生成する場合と同様です。
lex または yacc のソースを .c ファイルにコンパイルする暗黙の規則があります。.c ファイルは、C ソースからオブジェクトファイルをコンパイルする暗黙の規則を使用してさらに処理されます。ソースファイルに #include 文が含まれていない場合は、.c ファイルは中間ファイルとして使用されるため、保存しておく必要はありません。この場合は、.l.o の規則または .y.o の規則を使用してオブジェクトファイルを生成し、(派生した) .c ファイルを削除できます。
以下にメークファイルの例を示します。
CFLAGS= -O .KEEP_STATE: all: scanner parser scanner: scanner.o parser: parser.o
このメークファイルの結果は、以下のようになります。
$ make -n rm -f scanner.c lex -t scanner.l > scanner.c cc -O -c -o scanner.o scanner.c rm -f scanner.c yacc parser.y cc -O -c -o parser.o y.tab.c rm -f y.tab.c
lex と yacc を組み合わせて使用する場合は、より複雑になります。オブジェクトファイルが正しく機能するには、lex の C コードに yacc が生成したヘッダーが含まれている必要があります。yacc のソースファイルが変更されたときは、lex が生成した C ソースファイルを再コンパイルする必要があります。この場合は、yacc のソースが変更されるごとに lex を実行しなくてもよいように、lex が生成した中間 (.c) ファイルと、yacc が生成した .h ファイルを保存しておきます。
yacc は、y.tab.c と y.tab.h という名前の出力ファイルを生成します。出力ファイルのベース名をソースファイルと同じにしたい場合には、出力ファイルの名前を変更してください。
以下のメークファイルは、lex のソース、yacc のソース、C ソースファイルから構築したプログラムを管理します。
CFLAGS= -O .KEEP_STATE: a2z: c_functions.o scanner.o parser.o cc -o $@ c_functions.o scanner.o parser.o scanner.c: parser.c + parser.h: parser.y yacc -d parser.y mv y.tab.c parser.c mv y.tab.h parser.h
前述のように、複数の暗黙の規則の橋渡しをする規則は生成されないので、scanner.c のターゲットエントリを指定する必要があります。このエントリは、.l.c と .c.o の暗黙の規則の橋渡しをして scanner.o の依存関係リストから scanner.l を生成します。ターゲットエントリには規則がないため、暗黙の規則 .l.c を使用して scanner.c が構築されます。
次のターゲットエントリは、yacc の中間ファイルを生成する方法を指定しています。yacc -d を使用してヘッダーと C ソースファイルの両方を生成する暗黙の規則はないため、これを行う規則を指定するターゲットを記述する必要があります。
parser.c と parser.h のターゲットエントリにおいて、ターゲット名を区切る + 記号は、そのエントリがターゲットグループのエントリであることを示します。ターゲットグループとは複数のファイルの集まりで、それらすべてのファイルは規則の実行時に生成されます。 1 つのターゲットを構成している複数のファイルを、 1 つのグループとして扱います。+ 記号がない場合は、リスト中の各項目は独立したターゲットになります。ターゲットグループを使用すると、make は各ターゲットファイルについて別々に変更日時を調べ、ターゲットの規則は、make の実行 1 回につき必要な場合に 1 度だけ実行されます。
シェルスクリプトはテキストファイルですが、実行するには実行権が必要です。SCCS 管理のファイルからは実行権が削除されるため、SCCS ではシェルスクリプトとその「ソース」という区別を設けると便利です。make には、ソースからスクリプトを取り出すという暗黙の規則があります。シェルスクリプトのソースファイルの接尾辞は .sh です。スクリプトおよび .sh ソースファイルファイルの内容は同じですが、スクリプトには実行権が設定されており、.sh ソースファイルには設定されていません。make でのスクリプト用の暗黙の規則は、ソースファイルからスクリプトを取り出し、.sh ファイルを (必要に応じて取り出してから) 複製し、そのスクリプトファイルのアクセス権を変更して実行可能にします。以下に例を示します。
$ file script.sh script.sh: ascii text $ make script cat script.sh > script chmod +x script $ file script script: commands text
シェルスクリプトは、テストの実行や、対話式の (ユーザー入力が必要な) 処理あるいは make による依存関係の検査が不要な定型作業を行う際に便利です。特にテストでは、プログラムに対して端末から特定の入力を繰り返し行う必要があります。
ライブラリの場合は、さまざまな機能を実行するプログラムのセットを C で記述し、スクリプトからの特定の入力に応じて特定の順序で実行することができます。ユーティリティプログラムの場合は、機能を実行してその速度を測定するベンチマークプログラムを作成できます。いずれの場合も、各テストを実行するコマンドをシェルスクリプトに組み込み、繰り返しユーザーが行う処理をなくしたり、管理を簡単にすることができます。
テスト用スクリプトを開発した後、そのスクリプトを実行するためのターゲットは簡単に記述できます。スクリプト内では make の依存関係の検査は不要な場合がありますが、依存関係の検査を実行すると、テスト前にプログラムまたはライブラリを更新することができます。
以下に示すテストを実行するためのターゲットエントリでは、test が lib.a に依存しています。ライブラリが最新でない場合は、make はライブラリを再構築してテストを実行します。これにより、常に最新のバージョンを使用してテストが実行されます。
# テスト中のライブラリ LIBRARY= lib.a test: $(LIBRARY) testscript set -x ; testscript > /tmp/test.¥$¥$ testscript: testscript.sh test_1 test_2 test_3 # ライブラリ構築規則 $(LIBRARY): @ echo Building $(LIBRARY) (library-building rules here) # test_1 ... test_3 で複数のライブラリ関数を検査する test_1 test_2 test_3: $$@.c $(LIBRARY) $(LINK.c) -o $@ $<
test は、testscript にも依存しています。testscript は、3 つのテストプログラムに依存しています。
これにより、テストプログラムが make がテスト処理を実行する前に更新されます。lib.a は、メークファイルに含まれるターゲットエントリに従って構築されます。testscript は、.sh の暗黙の規則を使用して構築されます。各テストプログラム用にそれぞれソースファイルが 1 つずつある場合に、最後のターゲットエントリの規則を使用してテストプログラムが構築されます (これらのプログラムは、.c ファイルのほかに、適切なライブラリにもリンクする必要があるため、.c の暗黙の規則は適用されません)。
テスト用の規則に含まれる ¥$¥$ という文字列は、make が $ 記号を解釈しないように指定しています。make は、2 つの $ 記号をそのままシェルに渡します。シェルは、$$ をシェルのプロセス ID に展開します。これよって、各テストごとに異なる名前の一時ファイルに書き込むことが可能になります。set -x コマンドは、シェルが端末上で実行するコマンドをシェルに表示させます。これにより、テスト結果が記録されているファイルの実際の名前を確認することができます。
以下の例のように、規則内でシェルコマンドの置換を指定できます。
do: @echo `cat Listfile`
マクロ中では、逆引用符で囲まれた式を指定することもできます。
DO= `cat Listfile` do: @echo $(DO)
ただし、この形式のコマンド置換は規則内でのみ使用できます。
シェルコマンドをマクロの定義として指定する例を以下に示します。
COMMAND= cat Listfile
コマンド置換マクロ参照を使用して、参照をマクロ値に含まれるコマンド出力に置換するように make に指示できます。このコマンド置換は、メークファイル中の任意の箇所に記述できます。
COMMAND= cat Listfile $(COMMAND:sh): $$(@:=.c)
この例は、他のファイルからターゲットのリストを取り込み、各ターゲットが対応する .c ファイルに依存することを示します。
シェルコマンド置換と同様に、コマンド置換参照が評価されると、コマンドの標準出力結果に置換されます。復帰改行文字は、空白文字に変換されます。コマンドは、参照が検出されると実行されます。コマンドの標準エラーは無視されます。ただし、コマンドがゼロ以外の終了ステータスを返した場合は、make はエラーを表示して停止します。
これを回避するには、コマンド行の末尾に true コマンドを追加します。
COMMAND = cat Listfile ; true
以下の形式のマクロ代入は、command の標準出力を cmd_macro に代入します。以下に例を示します。
cmd_macro:sh = command
COMMAND:sh = cat Listfile $(COMMAND): $$(@:=.c)
この例は、前述の例と同じ結果になります。ただし、この例ではコマンドは make の実行ごとに 1 回だけ実行されます。この場合も、標準出力だけが使用され、復帰改行文字は空白文字に変換されます。また、コマンドがゼロ以外の終了ステータスを返した場合は、make はエラーを表示して停止します。
コマンド置換マクロ代入は、以下の形式で記述することもできます。
macro:sh += command
macro の値にコマンドの出力を追加します。
target := macro:sh = command
target およびその依存関係を処理するときに、command の出力として条件付き macro を定義します。
target := macro:sh += command
target およびその依存関係を処理するときに、command の出力を条件付き macro の値に追加します。