0. 序文
このブログは、Makefile 初心者が中級および上級レベルに移行するのを支援するのに適していますが、詳細なスキルは実践で蓄積する必要があります。
1. 基本的な考え方
1.1 目標、依存関係、ルール: Makefile の基本構造と動作原理を理解します。
Makefile では、ターゲット、依存関係、およびルールの概念が核を形成し、プロジェクトのファイルを構築および更新する方法を定義します。
ターゲット:
ターゲットは通常、ビルドされるファイルの名前を表します。実行可能ファイル、オブジェクト ファイル、またはラベル (疑似ターゲット) の場合もあります。
たとえば、次のルールでは、「program」がターゲットです。
program: main.o utils.o
gcc -o program main.o utils.o
依存関係:
依存関係は、ターゲットが依存するファイルです。ターゲットは、1 つ以上の依存関係がターゲットより新しい場合にのみ再構築されます。
上の例では、`main.o` と `utils.o` は `program` の依存関係です。
ルール:
ルールは、依存関係からターゲットを構築する方法を定義します。これは一連のコマンドであり、通常はシェル コマンドです。
上の例では、ルールは次のとおりです。
gcc -o program main.o utils.o
「make project」と入力すると、Make は「program」の依存関係が現在よりも新しいかどうかをチェックし、新しい場合はルールを実行してビルドします。
1.2 変数: Makefile で変数を設定および使用する方法を学びます。
Makefile では、変数を使用して値を保存し、再利用できます。これは、複数の場所で同じ値または構成が必要な場合に便利です。たとえば、複数のルールで同じコンパイラまたは同じコンパイラ フラグを使用したい場合があります。
変数を定義します。
変数を定義するための構文は簡単です。
VARIABLE_NAME = value
または
VARIABLE_NAME := value
2 つの主な違いは、`=` は遅延代入を使用し、使用されたときにのみその値を評価するのに対し、`:=` は即時代入を使用し、その値をすぐに評価することです。
変数を使用します。
Makefile で変数を使用するには、`$(VARIABLE_NAME)` または `${VARIABLE_NAME}` を使用できます。例えば:
CC = gcc
CFLAGS = -Wall
program: main.o utils.o
$(CC) $(CFLAGS) -o program main.o utils.o
ここでは、2 つの変数 `CC` と `CFLAGS` を定義し、ルールでそれらを使用します。こうすることで、コンパイラやフラグを変更する場合、Makefile 全体ではなく、1 か所のみ変更するだけで済みます。
その核心として、Makefile の目標、依存関係、およびルールによって、Make にプロジェクトのビルド方法が指示されます。変数を使用すると、これらのビルド ステップを再利用および構成する方法が提供され、Makefile がより柔軟で保守しやすくなります。これらの基本概念を理解することが、Makefile を作成して理解するための鍵となります。
2.パターンルール
パターン ルールは Makefile の強力なツールで、各ファイルを個別に定義するのではなく、ファイルのグループに対してルールを定義できます。%
これは、ファイル名の一部と一致するパターン (通常は文字) を使用することによって実現されます。
2.1 基本的なパターンのルール:
基本的なパターン ルールは次のようになります。
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
ここにあります:
- `%.o` はターゲット モードで、`.o` で終わるすべてのファイルを意味します。
- `%.c` は依存関係パターンであり、`.c` で終わるすべてのファイルを意味します。
- `$<` は、ルールの最初の依存関係 (この場合は `%.c` ファイル) を表す自動変数です。
- `$@` は、ルールのターゲット (この場合は `%.o` ファイル) を表す自動変数です。
このルールは、make に `.c` ファイルから `.o` ファイルを構築する方法を指示します。
たとえば、プロジェクトに「main.c」という名前のソース ファイルがあるとします。上記のパターン ルールにより、単に `make main.o` と入力するだけで、make は上記のルールを使用して `main.c` をコンパイルし、`main.o` を生成する方法を認識します。
2.2 組み込みのパターン ルール:
GNU Make は一連の組み込みのパターン ルールを提供します。つまり、多くの場合、一般的な操作 (`.c` ファイルから `.o` ファイルをコンパイルするなど) のルールを記述する必要さえありません。
これが、Makefile なしで make を使用できる理由でもあります。たとえば、「main.c」ファイルしかない場合は、「make main」と入力するだけで、GNU Make が組み込みのパターン ルールを使用してファイルをコンパイルします。
ただし、ほとんどのプロジェクトではさらにカスタマイズが必要なため、Makefile で独自のルールを定義することが有益なことがよくあります。
2.3 カスタムモードのルール:
基本的なパターン ルールに加えて、プロジェクト内の特定の状況を処理するために、より複雑なパターンを定義できます。
たとえば、ソース ファイルは `src` というディレクトリに保存されているが、オブジェクト ファイルは `build` ディレクトリに生成したいとします。次のパターン ルールを使用できます。
build/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@
ここで、ファイル `src/main.c` があると仮定して、`make build/main.o` と入力すると、上記のルールを使用して `src/main.c` から `build/main.o` が生成されます。 。
パターン ルールは、Makefile に、多くのファイルの共通のビルド ルールと更新ルールを定義するための効率的かつ簡潔な方法を提供します。ファイル名の一部と一致するパターンを使用することで、ルール定義をより柔軟にします。
3. 機能
GNU Make は、変数やファイル名などをより柔軟に操作するための一連の関数を提供します。次の例は、一般的な関数の使用法を示しています。
3.1 $(ワイルドカード ...)
この関数は、指定されたパターンに一致するファイルのリストを取得するために使用されます。たとえば、現在のディレクトリ内のすべての .c ファイルを取得するには、次のようにします。
SOURCES = $(wildcard *.c)
複数レベルのディレクトリにあるすべての `.c` ファイルを取得したい場合は、以下を使用できます。
SOURCES = $(wildcard src/**/*.c)
3.2 $(パツブスト ...)
パターンの置換を行う機能です。一般的な使用法は、ある種類のファイル リストを別の種類のファイル リストから生成することです。たとえば、「.c」ファイル リストから対応する「.o」ファイル リストを生成するには、次のようにします。
SOURCES = $(wildcard *.c)
OBJECTS = $(patsubst %.c,%.o,$(SOURCES))
ここで、`$(patsubst %.c,%.o,$(SOURCES))` は、`SOURCES` 内の各 `.c` ファイルを対応する `.o` ファイルに変換します。
3.3 $(foreach ...)
この関数はループ処理に使用されます。リスト内の各項目に対してアクションを実行します。たとえば、各名前に挨拶するには次のようにします。
NAMES = Alice Bob Charlie
GREETINGS = $(foreach name,$(NAMES),Hello $(name)!)
これにより、「こんにちは、アリス! こんにちは、ボブ! こんにちは、チャーリー!」という内容の `GREETINGS` 変数が生成されます。
3.4 $(filter ...) と $(filter-out ...)
$(filter pattern...,text) は、`text` 内の `pattern` に一致するすべての単語を返します。
FILES = foo.c bar.js baz.c
C_FILES = $(filter %.c,$(FILES))
これはすべての `.c` ファイル、つまり `foo.c baz.c` を返します。
`$(filter-out pattern...,text)` は `$(filter ...)` の逆で、`pattern` に一致しない単語を返します。
FILES = foo.c bar.js baz.c
NOT_C_FILES = $(filter-out %.c,$(FILES))
これは、「.c」ファイルではないファイル、つまり「bar.js」を返します。
3.5 ファイル名の操作
- `$(dir names...)`: 各ファイル名のディレクトリ部分を返します。たとえば、`$(dir src/foo.c bin/bar)` は `src/ bin/` を返します。
- `$(notdir names...)`: 各ファイル名のディレクトリ以外の部分を返します。たとえば、`$(notdir src/foo.c bin/bar)` は `foo.c bar` を返します。
- `$(サフィックス名...)`: 各ファイル名のサフィックスを返します。たとえば、「$(suffix src/foo.c bin/bar.js)」は「.c .js」を返します。
- `$(basename names...)`: 接尾辞なしで各ファイル名を返します。たとえば、`$(basename src/foo.c bin/bar.js)` は `src/foo bin/bar` を返します。
GNU Make のこれらの機能は、Makefile 作成者に豊富なツールを提供し、ファイル操作、パターン マッチング、およびテキスト処理をシンプルかつ柔軟にします。これらの関数がどのように動作するかを理解することが、複雑な Makefile を作成する鍵となります。
4. 自動変数
Makefile では、自動変数を使用すると、ルールのターゲット、依存関係、その他の関連コンテンツを明示的に指定せずに参照できるようになります。これにより、Makefile の柔軟性と再利用性が向上します。
4.1 $@: 対象ファイル名
この変数はルールのターゲットを表します。次のルールを考慮してください。
my_program: main.o utils.o
gcc -o $@ $^
ここで、「$@」は「my_program」を表します。
4.2 $^: すべての依存ファイル、重複なし
この変数はすべての依存ファイルを表しますが、繰り返されません。上記の例を続けます。
my_program: main.o utils.o
gcc -o $@ $^
ここで、「$^」は「main.o utils.o」を表します。重複する依存関係がある場合、`$^` はそれらを 1 回だけリストします。
4.3 $<: 最初の依存ファイル
この変数は、ルール内の最初の依存ファイルを表します。これはパターン ルールで特に役立ちます。例えば:
%.o: %.c
gcc -c $< -o $@
このルールでは、`main.o` 用にビルドしている場合、`$<` は `main.c` を表します。
4.4 $(?): ターゲットより新しいすべての依存ファイル
この変数は、ターゲットより新しい依存関係ファイルをすべてリストします。これは、特定の依存関係によって変更された部分のみを再ビルドする場合に、増分ビルドで役立ちます。
次のルールを考慮してください。
my_program: main.o utils.o
gcc -o $@ $?
`my_program` が最後に構築されてから変更されたファイルが `utils.o` だけである場合、`$?` は `utils.o` のみを表します。
自動変数を使用すると、Makefile 内のルールの目標、依存関係、その他の関係を参照するための簡潔かつ柔軟な方法が提供されるため、明示的に複数回指定する必要がなくなります。これらの自動変数を理解し、効果的に使用すると、Makefile が簡素化され、保守性が向上します。
5. 条件付き実行
Makefile では、特定の条件に基づいてさまざまなアクションを実行したい場合があります。たとえば、現在のオペレーティング システムやコンパイラ オプションに基づいてビルド パラメータを変更したい場合があります。この要件をサポートするために、GNU Make は一連の条件付き命令を提供します。
5.1 ifeq (arg1, arg2)
この命令は、2 つのパラメータが等しいかどうかをチェックするために使用されます。それらが等しい場合、`ifeq` ブロック内のコードが実行されます。
ifeq ($(CC),gcc)
CFLAGS = -Wall
else
CFLAGS = -O2
endif
上記の例では、変数 `CC` の値が `gcc` の場合、`CFLAGS` は `-Wall` に設定され、それ以外の場合は `-O2` に設定されます。
5.2 ifneq (arg1, arg2)
これは、2 つの引数が等しくないかどうかをチェックする `ifeq` の逆です。
ifneq ($(DEBUG),true)
CFLAGS = -O2
else
CFLAGS = -g
endif
「DEBUG」変数が「true」に等しくない場合、「CFLAGS」は「-O2」に設定され、それ以外の場合は「-g」に設定されます。
5.3 ifdef 変数名
このディレクティブは、変数が定義されているかどうかを確認します。この変数が定義されている場合、ifdef ブロックの内容は、その値に関係なく (たとえ空であっても) 実行されます。
ifdef OUTPUT_DIR
ODIR = $(OUTPUT_DIR)
else
ODIR = ./build
endif
`OUTPUT_DIR` 変数が定義されている場合、`ODIR` はその値に設定され、そうでない場合はデフォルトの `./build` になります。
5.4 ifndef 変数名
これは、変数が定義されていないかどうかをチェックする `ifdef` の逆です。
ifndef CC
CC = gcc
endif
変数 `CC` が定義されていない場合は、デフォルトで `gcc` が使用されます。
条件付きディレクティブを使用すると、特定の条件に基づいて Makefile でさまざまなアクションを実行できます。これにより、特にさまざまなプラットフォーム、コンパイラ、構成オプションを扱う場合に、Makefile 作成者に大きな柔軟性が提供されます。これらの条件ディレクティブを理解し効果的に使用すると、Makefile をさまざまなビルド環境やニーズにさらに適応できるようになります。
6. 他の Makefile をインクルードする
大規模なプロジェクトでは、Makefile が非常に大きく複雑になる可能性があります。可読性と保守性を向上させるために、Makefile を複数の小さなファイルに分割し、これらのファイルをメインの Makefile に含めるのが一般的です。GNU Make は、この目的のために `include` ディレクティブを提供します。
6.1 「include」の使用
基本的な構文は次のとおりです。
include filename
たとえば、「variables.mk」、「rules.mk」、および「config.mk」という 3 つの追加の Makefile があると仮定すると、次のように含めることができます。
include variables.mk rules.mk config.mk
これにより、これら 3 つのファイルの内容が指定された順序でインポートされます。
6.2 動的に生成された Makefile
他のルールによって生成された Makefile を含めることもできます。これは自動依存関係管理に非常に役立ちます。たとえば、gcc の `-M` オプションを使用して依存関係を生成し、それらを `.d` ファイルに保存できます。
-include $(DEPFILES)
`-include` は `include` とほぼ同じですが、ファイルが存在しない、またはその他のエラーがあるために失敗することはありません。
6.3 条件付き包含
`include` を Make の条件文と組み合わせて使用すると、特定の条件に基づいてファイルを含めるかどうかを決定できます。例えば:
ifeq ($(DEBUG),true)
include debug.mk
else
include release.mk
endif
「DEBUG」変数の値に応じて、「debug.mk」または「release.mk」のどちらを含めるかを決定できます。
6.4 ワイルドカード
ワイルドカードを `include` と組み合わせて使用することもできます。たとえば、すべての `.mk` ファイルをインクルードするには、次のようにします。
include *.mk
または、すべての Makefile をサブディレクトリに含めるには、次のようにします。
include */*.mk
「include」ディレクティブを使用すると、Makefile の作成者はその内容を複数のファイルに分割できるため、可読性と保守性が向上します。これにより、ルール、変数定義、その他のビルド ロジックを再利用する機会も提供されます。大規模なプロジェクトでは、この機能を効果的に使用すると、Makefile 構造を明確でよく整理された状態に保つことができます。
7. 擬似ターゲットと.PHONY
Makefile では、ほとんどのターゲットはオブジェクト ファイル、ライブラリ、実行可能ファイルなどのファイルを指します。ただし、一部のターゲットはファイルを表すのではなく、実行される一連のコマンドを表します。ファイルを表さないこのようなターゲットは「擬似ターゲット」と呼ばれます。
擬似ターゲットは、ビルド アーティファクトのクリア、ドキュメントの生成、テストの実行など、一連のコマンドを実行するためによく使用されます。疑似ターゲットはファイルではないため、ファイルの存在や変更日の影響を受けません。
たとえば、一般的な「clean」目標は、通常、生成されたすべてのファイルを削除するために使用されます。
clean:
rm -f *.o my_program
「make clean」を実行すると、「clean」という名前のファイルが存在するかどうかに関係なく、関連付けられたコマンドが実行されます。
7.1 .フォニー
問題は、「clean」という名前のファイルまたはディレクトリがある場合、「make clean」が期待どおりに動作しない可能性があることです。これは、make がファイルの変更日をチェックするためです。ターゲットが疑似ターゲット (ファイルではない) であることを make に伝えるには、`.PHONY` 宣言を使用します。
上の例では、「clean」が常に疑似ターゲットとして扱われるようにするには、次のようにします。
.PHONY: clean
clean:
rm -f *.o my_program
このようにすると、`clean` という名前のファイルが存在する場合でも、`make clean` は正しく動作します。
Makefile 内の複数の疑似ターゲットに `.PHONY` を使用できます。例えば:
.PHONY: clean install test
clean:
rm -f *.o my_program
install:
cp my_program /usr/local/bin/
test:
./test_script.sh
擬似ターゲットは、ファイルを表すのではなく、実行される操作を表す Makefile 内の特別なターゲットです。`.PHONY` 宣言を使用すると、make はこれらのターゲットをファイルではなく疑似ターゲットとして正しく扱うことができます。これは、コマンドが期待どおりに実行されることを確認するために重要です (特に、疑似ターゲットと同じ名前のファイルが存在する可能性がある場合)。
8. 沈黙のルールと特別な目標
デフォルトでは、`make` がルール内のコマンドを実行するとき、実行前にそれらのコマンドをエコー (つまり、出力) します。これは Makefile をデバッグする場合には便利ですが、一般に使用すると混乱をきたしたり、出力が冗長になりすぎたりする可能性があります。
コマンドがエコーされないようにするには、コマンドの前に「@」記号を置きます。例えば:
quiet:
@echo This will be printed without showing the command
Makefile で「make Quiet」を実行すると、次の内容のみが表示されます。
This will be printed without showing the command
の代わりに:
echo This will be printed without showing the command
This will be printed without showing the command
Makefile 全体でコマンドのエコーを無効にするには、特別なターゲット `.SILENT` を使用できます。
.SILENT:
quiet:
echo This will also be printed without showing the command
8.1 特別な目標
Make は、特定の意味を持つ一連の特別なターゲットを提供しますが、実際のビルド ターゲットではありません。
.Suffixes
`.SUFFIXES` は、古い形式の暗黙的な依存関係ルールで使用するファイル接尾辞のセットを定義します。たとえば、Make はデフォルトで、「.c」 ソース ファイルを「.o」 オブジェクト ファイルにコンパイルできることを認識しています。`.SUFFIXES` を使用すると、すべてのサフィックスをクリアしたり、新しいサフィックスを追加したりできます。
# 清除所有的默认后缀
.SUFFIXES:
# 只定义.c和.o为有效的后缀
.SUFFIXES: .c .o
最近の Makefile では、多くの人がサフィックス ルールよりもパターン ルールを使用することを好みますが、特に古い Makefile を扱う場合には、この特別な目的を知っておくと役立ちます。
。デフォルト
make がビルド ターゲットのルールを見つけられない場合、`.DEFAULT` ルールを使用しようとします。これは、未定義のターゲットを捕捉するのに役立ちます。
.DEFAULT:
@echo "Unknown target: $@"
上記の `.DEFAULT` ルールは、Makefile で定義されていないターゲットをビルドしようとするとメッセージを出力します。
Quiet ルールを使用すると、Makefile コマンドのエコーを制御できます。`.SUFFIXES` や `.DEFAULT` などの特別なターゲットは Makefile に対する追加の制御を提供し、特定の状況を処理する方法を定義したり、make のデフォルトの動作を変更したりすることができます。これらの機能を理解して利用すると、より強力で柔軟な Makefile を作成するのに役立ちます。
9. 高度な依存関係
大規模なプロジェクトでは、ソース ファイル間の依存関係が複雑になる可能性があります。たとえば、C++ ソース ファイルには複数のヘッダー ファイルが含まれ、さらにそのヘッダー ファイルには他のヘッダー ファイルが含まれる場合があります。ヘッダー ファイルが変更された場合は、それに関連付けられたソース ファイルを再コンパイルする必要があります。
これらの依存関係を手動で追跡して Makefile に入れることは、特にファイルが頻繁に変更される場合には現実的ではありません。幸いなことに、このプロセスは自動化できます。
9.1 動的な依存関係の生成
コンパイラー (GCC など) は、依存関係情報を生成するオプションを提供することがよくあります。GCC の場合、これは `-M` とそのバリアントです。
たとえば、「example.c」という名前のソース ファイルの場合、次のコマンドを実行します。
gcc -M example.c
`example.c` ファイルの依存関係は、通常は次の形式で出力されます。
example.o: example.c header1.h header2.h ...
これらの依存関係をファイルに保存するには、「-MF」オプションを使用できます。通常、これらの依存関係は「.d」ファイルに保存されます。例えば:
gcc -M -MF example.d example.c
これにより、`example.c` のすべての依存関係を含む `example.d` という名前のファイルが生成されます。
9.2 Makefile での動的依存関係の使用
`.d` ファイルが生成されたら、ソース ファイルをいつ再コンパイルするかを make が認識できるように、それらを Makefile に含める必要があります。
これらの依存関係ファイルを含めるには、`-include` (または `include`) ディレクティブを使用します。
SRC = example.c
OBJ = $(SRC:.c=.o)
DEP = $(OBJ:.o=.d)
my_program: $(OBJ)
gcc $^ -o $@
-include $(DEP)
ここにあります:
- まず、ソース ファイル、オブジェクト ファイル、および依存ファイルのリストを定義します。
- ターゲット ルールでは、通常どおりプログラムをコンパイルするだけです。
- すべての `.d` ファイルをインクルードするには、`-include $(DEP)` を使用します。`-include` はエラーフリーなので、依存ファイルが存在しない場合でも make はエラーを報告しません。
9.3 動的な依存関係の生成とコンパイルの組み合わせ
一般的な方法は、ソース ファイルのコンパイル中に依存関係情報を生成することです。GCC には、コンパイル中に `.d` ファイルを自動的に作成するための `-MMD` オプションが用意されています。
%.o: %.c
gcc -c -MMD $< -o $@
動的な依存関係を使用すると、ヘッダー ファイルやその他の依存関係が変更されたときに、影響を受けるソース ファイルのみが再コンパイルされるようにすることができます。これにより、不必要なコンパイルが削減されるだけでなく、ビルドの正確性も保証されます。大規模なプロジェクトでは、この種の自動依存関係追跡が非常に重要です。
10. 搭載機能と先進技術
Makefile は、開発者が高度な操作や処理を実行できるようにする一連の強力な組み込み関数を提供します。これらの関数の一部について詳しく説明します。
10.1 $(評価 ...)
`$(eval ...)` 関数を使用すると、Makefile コードを動的に評価できます。これは、Makefile の実行中に新しいルールや変数などを生成および評価できることを意味します。これはかなり高度な機能であり、通常はより複雑な Makefile シナリオで使用されます。
たとえば、動的に生成されるファイルのリストのルールを作成するとします。
FILES = file1 file2
define RULES
$(1).o: $(1).c
gcc -c $(1).c -o $(1).o
endef
$(foreach file, $(FILES), $(eval $(call RULES, $(file))))
上記のコードは、`file1` と `file2` のコンパイル ルールを定義していますが、これらのファイル名は他の場所から動的に生成されます。
10.2 $(コール...)
`$(call ...)` 関数を使用すると、make マクロを呼び出すことができます。これは、再利用可能なコード ブロックを定義し、パラメーターを使用して呼び出す方法です。
上の `$(eval ...)` の例では、`$(call ...)` の使用法を見てきました。ここで、「RULES」は 1 つの引数 (ファイル名) を受け取り、そのファイルのコンパイル規則を定義するマクロです。
より簡単な例を次に示します。
define say_hello
@echo Hello, $(1)!
endef
greet:
$(call say_hello, world)
`makegreet` を実行すると、「Hello, world!」と出力されます。
10.3 $(値 ...)
`$(value ...)` 関数は変数のプレーン テキスト値を返し、変数や関数の参照は実行しません。
次の例を考えてみましょう。
VAR = value of VAR
ANOTHER_VAR = $(VAR)
result1:
@echo $(ANOTHER_VAR)
result2:
@echo $(value ANOTHER_VAR)
ここで、「result1」と「result2」はどちらも「VARの値」を出力します。ただし、「$(value ...)」の使用には、特に複数の参照または再帰参照を含む変数を扱う場合に利点があります。`$(value ...)` は、変数の内容を評価したり拡張したりせずに、変数の内容を直接取得します。
Make の組み込み機能と高度な技術により、開発者は複雑なビルド シナリオや要件に対処するための高い柔軟性が得られます。これらの機能を理解して習得することで、Makefile をより効果的に整理および管理できるようになり、ビルド プロセスを最適化できます。
11. 高度なトピック
ここでは、大規模または複雑なプロジェクトをより効率的に構築するのに役立つ高度な Makefile 機能とテクニックをいくつか紹介します。
11.1 並列ビルド: `-j` パラメータを使用する
デフォルトでは、`make` は一度に 1 つのターゲットを処理します。しかし、マルチコア/マルチスレッドを備えた最新のコンピューターでは、複数のターゲットを同時に処理できるため、ビルド プロセスが高速化されます。これは、`make` の `-j` パラメータによって実現され、同時に実行できるタスクの最大数を指定できます。
たとえば、最大 4 つの並列タスクを許可するには、次を実行できます。
make -j4
注: 並列ビルドでは、Makefile が「スレッドセーフ」である必要があります。これは、どの目標も別の目標の副作用に依存しないことを意味します。すべての依存関係を正しく宣言することが重要です。
11.2 `VPATH` と `vpath` を使用したファイルの検索
大規模なプロジェクトでは、ソース ファイルが複数のサブディレクトリに編成される場合があります。`VPATH` と `vpath` は、ファイルの検索場所を指定するために `make` によって提供される 2 つのメソッドです。
`VPATH`: は、`make` がファイルを見つける必要があるときに検索するディレクトリのコロンで区切られたリストです。
VPATH = src:../headers
上記の設定は、`make` に、`src` ディレクトリと `../headers` ディレクトリ内のファイルを検索するように指示します。
`vpath`: より詳細な制御を提供し、特定のファイル モードのディレクトリを指定できるようにします。
vpath %.h ../headers
これは、`make` に、`../headers` ディレクトリ内の `.h` ファイルのみを検索するように指示します。
11.3 `make` でのダブルコロンルールの使用
通常、Makefile ではターゲットごとに 1 つのルールを使用します。ただし、場合によっては、ターゲットに複数のルールを適用したい場合があります。ここで二重コロンのルールが登場します。
ルールを定義するには、単一コロンの代わりに二重コロン (`::`) を使用してください。各二重コロン ルールは独立しており、ターゲットが存在しない場合、またはその依存関係よりも古い場合にのみ実行されます。
例えば:
file.txt:: source1.txt
cat source1.txt > file.txt
file.txt:: source2.txt
cat source2.txt >> file.txt
どちらのルールも「file.txt」に適用されます。「make file.txt」を実行すると、両方のルールが順番に実行されるため、「file.txt」には最初に「source1.txt」の内容が含まれ、次に「source2.txt」の内容が含まれます。
上記で説明した高度なヒントと機能は、特に大規模または複雑なコード ベースを扱う場合に、ビルド プロセスをより適切に管理および最適化するのに役立ちます。これらの機能を正しく理解して使用すると、Makefile をより強力かつ柔軟にすることができます。
12. ベストプラクティス
Makefile を作成および管理する場合、いくつかのベスト プラクティスに従うことで、ビルド プロセスの効率性と保守性を確保できます。
12.1 大規模な Makefile の整理とモジュール化
プロジェクトが成長するにつれて、Makefile は非常に大きく複雑になる可能性があります。この場合、モジュール化して論理ブロックに編成することが合理的です。
- コードを複数の Makefile に分割する: 大規模なプロジェクトの場合は、Makefile を複数のサブ Makefile に分割し、各サブ Makefile がプロジェクトの特定の部分を担当することを検討してください。次に、「include」ディレクティブを使用してそれらをメインの Makefile に含めます。
- コメントの使用: Makefile で、「#」で始まる行を使用してコメントを追加します。これは、ターゲット、変数、その他のコード部分の目的と動作を説明するのに役立ちます。
- 機能ごとに整理する: 関連する目標、変数、ルールをグループ化し、空白行とコメントを使用して各セクションのコンテキストを提供します。
12.2 再帰的 Make の問題
大規模なプロジェクトでは、再帰的な `make` の使用がよく見られます。これは、ある Makefile から `make` を呼び出して、別のサブディレクトリにターゲットをビルドすることを意味します。これは場合によっては便利ですが、問題が発生することもよくあります。
- 依存関係の問題: 再帰的な「make」により、異なる Makefile 間で依存関係が失われる可能性があり、その結果、ビルドが正しく行われない可能性があります。
- パフォーマンスの問題: サブディレクトリごとに新しい `make` インスタンスを開始すると、ビルド時間が長くなります。
非再帰的なトリック:
これらの問題を回避するには、非再帰的なトリックを使用できます。
- 単一の Makefile: これは直観に反するように聞こえるかもしれませんが、ほとんどのプロジェクトでは、複数の再帰的 Makefile を使用するよりも単一の Makefile (おそらく「include」ディレクティブによってモジュール化されている) を使用する方が効率的で管理が簡単です。
- `VPATH` または `vpath` の使用: これらの変数を使用すると、`make` がソース ファイルを検索する場所を指定できるため、単一の Makefile で複数のディレクトリからコードをビルドできます。
12.3 移植性を考慮した設計
プロジェクトが別のシステムまたはプラットフォーム上でビルドされる可能性がある場合は、Makefile が移植可能であることを確認してください。
- 条件付き代入を使用する: 条件付き代入を使用して、さまざまなシステムまたは条件に基づいて変数を設定します。
- ハードコードされたパスを避ける: ハードコードされたパスやツール名の代わりに Makefile 変数を使用します。
Makefile を適切に整理して構築すると、プロジェクトの構築効率と保守性が大幅に向上します。再帰と非再帰の長所と短所を必ず理解し、Makefile の明瞭さと移植性を最大限に高める方法を常に検討してください。
13. 学習リソースと実践方法
13.1 学習リソース
- `man make`: GNU Make のマニュアル ページを表示します。
- [GNU Make Manual](https://www.gnu.org/software/make/manual/make.html): 公式ドキュメントには、詳細な情報と例が記載されています。
13.2 練習
Makefile を学ぶ鍵は練習です。単純な Makefile の作成を開始し、徐々に複雑さを増していきます。自分のプロジェクトまたは他のオープンソース プロジェクト用に Makefile を作成または最適化してみてください。
問題が発生したり、何かがわからない場合は、ドキュメントを確認したり、関連する質問を検索したり、コミュニティに質問したりしてください。徐々に深めて練習し、最終的には上級レベルに到達します。