初心者のためのMakefileとGNU Makeの簡単ガイド
GNU Makeは、特定のコードベースのどの部分を再コンパイルする必要があるかを判断し、それらの操作を実行するためにLinuxコマンドを発行できる開発ユーティリティです。
このビルド自動化ツールは、シェルからコマンドを発行してコンパイルを行える任意のプログラミング言語で使用できるため、C、C++、およびその他多くのコンパイル言語にとって非常に有用です。
LinuxのMakefile
GNU Make を使用するには、プログラム内のさまざまなファイル間の関係を定義するルールと、各ファイルを更新するためのコマンドのセットが必要です。これらは、特殊なファイルである ‘Makefile‘ または ‘makefile‘ に書き込まれます。
「make」コマンドは、makefile データベースとファイルの最終更新日時を使用して、どのファイルを再コンパイルする必要があるかを決定します。
Makefile の内容
一般的に、makefileには5種類の要素が含まれます:明示的ルール、暗黙的ルール、変数定義、ディレクティブ、およびコメント。
- 明示的ルールは、1つ以上のファイル(
targetsと呼ばれる)を作成または再作成する方法と、その実行時期を指定します。 - 暗黙のルールは、ファイル名に基づいてファイルを作成または再作成する方法を指定し、ターゲットファイルが名前の似た他のファイルとどのように関連しているかを説明します。
- 変数定義は、後でMakefile内で置き換えられる変数の文字列値を指定する行です。
- ディレクティブとは、makefileを読み込む際にmakeに特別なことをさせるための指示です。
- コメントは
'#'記号で始まります。'#'で始まる行は、makeによって無視されます。
Makefileの構造
システムを再コンパイルする方法を make に教える情報は、メイクファイルを読むことで得られる。
簡単なMakefileは、次の構文を持つルールで構成されます:
target ... : prerequisites ...
recipe
...
...
- ターゲットは、プログラムによって生成される出力ファイルであり、偽のターゲット(以下で説明)である場合もあります。例としては、実行可能ファイル、オブジェクトファイル、または
clean、install、test、allのような偽のターゲットがあります。 - 前提条件(依存関係とも呼ばれる)は、ターゲットファイルを作成するための入力として使用されるファイルです。
- レシピとは、前提条件に基づいて対象ファイルを作成するためにmakeが実行する操作のことです。各レシピ行の前にはタブ文字を置く必要があります。ただし、異なる文字をプレフィックスとして定義するために
.RECIPEPREFIX変数を指定する場合は除きます。
サンプルMakefile:
final: main.o end.o inter.o start.o
gcc -o final main.o end.o inter.o start.o
main.o: main.c global.h
gcc -c main.c
end.o: end.c local.h global.h
gcc -c end.c
inter.o: inter.c global.h
gcc -c inter.c
start.o: start.c global.h
gcc -c start.c
clean:
rm -f main.o end.o inter.o start.o final
この例では、4つのCソースファイルと2つのヘッダーファイルを使用して、実行可能な最終ファイルを作成します。各 '‘.o’' ファイルは、makefile 内でターゲットであり依存関係でもあります。最後のターゲット clean に注目してください ― これは実際のファイルではなくアクションです。
通常、コンパイル中にクリーンする必要がないため、他のルールでは前提条件として書かれていません。ファイルを参照せず、単にアクションであるターゲットは偽のターゲットと呼ばれます。それらは通常、通常のターゲットファイルのような前提条件を持ちません。
GNU Make が Makefile を処理する方法
デフォルトでは、make は、メイクファイルの最初のターゲット、すなわち デフォルト目標 から開始します。私たちの例では、final が最初のターゲットです。その前提条件にはオブジェクトファイルが含まれるため、final を作成する前にそれらを更新する必要があります。各前提条件はそれぞれのルールに従って処理されます。
ソースファイルやヘッダーファイルに変更が加えられた場合、またはオブジェクトファイルがまったく存在しない場合、再コンパイルが行われます。必要なオブジェクトファイルを再コンパイルした後、makeはfinalを再リンクするかどうかを決定します。これは、finalが存在しない場合、またはオブジェクトファイルのいずれかがfinalより新しい場合に発生します。
例えば、inter.c を変更して make を実行すると、そのソースファイルが再コンパイルされて inter.o が更新され、その後 final がリンクされます。
Makefileで変数を使う
私たちの例では、final のルールでオブジェクトファイルをすべて二度リストする必要がありました:
final: main.o end.o inter.o start.o
gcc -o final main.o end.o inter.o start.o
このような重複を避けるために、ファイルのリストを格納するための変数を導入できます。変数を使用すると、Makefile の保守も容易になります。
こちらは改良版です:
CC = gcc
CFLAGS = -Wall -Wextra -O2
OBJ = main.o end.o inter.o start.o
TARGET = final
$(TARGET): $(OBJ)
$(CC) -o $(TARGET) $(OBJ)
main.o: main.c global.h
$(CC) $(CFLAGS) -c main.c
end.o: end.c local.h global.h
$(CC) $(CFLAGS) -c end.c
inter.o: inter.c global.h
$(CC) $(CFLAGS) -c inter.c
start.o: start.c global.h
$(CC) $(CFLAGS) -c start.c
clean:
rm -f $(OBJ) $(TARGET)
コンパイラ用の CC とコンパイルフラグ用の CFLAGS を定義していることにも注目してください。これにより、コンパイラのオプションを変更したり、プロジェクト全体でコンパイラを切り替えたりするのが簡単になります。
ソースディレクトリのクリーンアップルール
例に見られるように、コンパイル後に不要なファイルを削除することで、ソースディレクトリを整理するルールを定義できます。しかし、実際に clean という名前のファイルがあると仮定した場合、ファイル と ターゲット をどのように区別できますか?ここで phony ターゲットの出番です。
偽のターゲットは実際にはファイルの名前ではなく、明示的に実行が要求されたときに実行されるレシピの名前にすぎません。偽のターゲットを使用する主な理由は、同じ名前のファイルとの競合を避けることと、パフォーマンスを向上させることです。
重要な詳細があります:clean のレシピは、make を実行したときにデフォルトでは実行されません。代わりに、make clean を明示的に呼び出す必要があります。
正しく偽のターゲットを宣言するには、.PHONYディレクティブを使用します:
.PHONY: clean all install
clean:
rm -f $(OBJ) $(TARGET)
all: $(TARGET)
モダンなMakefileのベストプラクティス
考慮すべきいくつかの現代的な実践は次の通りです:
繰り返しを減らすために、パターンルールを使用します:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
デフォルトのallターゲットを追加:
.PHONY: all
all: $(TARGET)
自動ヘッダートラッキングのために依存関係生成を含める:
DEPS = $(OBJ:.o=.d)
-include $(DEPS)
一般的な操作のために、さらに偽のターゲットを追加します:
.PHONY: install test run
ルールをより一般的にするために、$@(ターゲット)、$>(最初の前提条件)、および$^(すべての前提条件)のような自動変数を使用します。
結論
自分のコードベースのためにメイクファイルを作成してみてください。GNU Makeは依然として強力で広く使われているビルドツールであり、特にCやC++のプロジェクトに適しています。
Makefileを理解することは、無数のオープンソースプロジェクトで作業するのに役立ち、ビルドプロセスを細かく制御できるようになります。質問や経験があれば、ぜひコメントしてください!