Makefile の書き方

2009年12月19日

はじめに

この文書では、GNU Make 用 Makefile の書き方を説明します。

make について

make は、主に、複数のソースコードからなるプログラムをコンパイルするために使われます。make を実行すると Makefile が読み込まれます。Makefile にはコマンドの実行規則が書かれていて、make はそれに従ってコマンドを実行していきます。

より一般的に言うと、make は、「依存しあった複数のファイルから生成されるもの」を生成するための仕組みを提供するプログラムです。Makefile の内容は、ファイルの依存関係とコマンドで構成されます。

基本書式

ターゲット: 依存ターゲット
	コマンド

コマンドの前の空白は <Tab> です。ターゲットを指定して make を実行すると、依存ターゲットの生成規則を実行してから 2 行目のコマンドを実行します。ターゲットを指定せずに make を実行すると、一番上のターゲットの生成規則を実行します。Makefile の内容が以下のような場合を考えましょう。

a: b
	@echo "a"

b:
	@echo "b"

この場合、make を実行すると "b", "a" の順で文字が表示されます。ターゲット指定なしで make を実行すると、一番上のターゲット a の生成規則が実行されますが、これがターゲット b に依存しているため、先に b の生成規則が実行されます。そこで "b" が表示され、その後 a のコマンドにより "a" が表示されるという次第です。

もし依存ターゲットの生成規則が書かれていない場合、make はそれをファイル名だと解釈し、ファイルを探します。たとえば、以下の Makefile が場合

a: b
	touch a
依存ターゲット b の生成規則がないので、怒られます。
$ make
make: *** No rule to make target `b', needed by `a'.  Stop.
ファイル b を用意してやると
$ touch b
$ make
touch a
問題なく実行されます。ここでもう一度 make を実行すると、以下のようになります。
$ make
make: `a' is up to date.

a の更新は必要ない、ということです。これはどういうことかというと、この場合、ターゲット a, b はファイル名として解釈されており、ファイル a と b の最終更新日時を比較して、b の更新日時が a のそれより新しい場合だけ a の生成規則を実行します。したがって、ファイル b の内容を更新したり、b を作り直したりすると、a が生成しなおされます。

$ touch b
$ make
touch a

コマンドは必ずしも指定する必要はないので、以下のような書き方もできます。

all: a b

a:
	touch a
b:
	touch b

この場合、一度 make を実行すると、ファイル a, b いずれかが削除されないかぎり all は実行されません。ターゲット a, b には依存ターゲットがないので、ファイル a, b がそれぞれ存在するかぎり、それらのターゲットは実行されません。

ちなみに、"#" から行末まではコメントになります。また、改行前にバックスラッシュを置くことで、改行しても 1 行とみなされます。

例: プログラムのコンパイル

より具体的な例で考えてみましょう。C 言語で書かれたプログラムのコンパイルが、以下のように実行されるとします。

$ gcc -o prog file1.c file2.c file3.c

上のようなコンパイルの仕方だと、file1.c だけを変更した場合でも file2.c, file3.c のコンパイルがやり直しになります。これを避けるためには、以下のように実行します。

$ gcc -c file1.c
$ gcc -c file2.c
$ gcc -c file3.c
$ gcc -o prog file1.o file2.o file3.o

こうしておけば、たとえば file1.c を書き直したなら

$ gcc -c file1.c
$ gcc -o prog file1.o file2.o file3.o
file3.c を書き直したなら
$ gcc -c file3.c
$ gcc -o prog file1.o file2.o file3.o
とすればよいことになります。

別の例として、file1.c, file2.c, file3.h からなるプログラムをコンパイルする場合を考えましょう。file2.c が file3.h をインクルードしているとします。

$ gcc -c file1.c
$ gcc -c file2.c
$ gcc -o prog file1.o file2.o

file1.c を書き直した場合は、次のようにコンパイルします。

$ gcc -c file1.c
$ gcc -o prog file1.o file2.o

file3.h を書き直した場合はどうでしょう。file3.h は file2.c に依存されているので、file2.c をコンパイルしなおす必要があります。

$ gcc -c file2.c
$ gcc -o prog file1.o file2.o

以上、依存しあった複数のファイのコンパイルを効率よく実行するには、そのつど判断が必要であり、効率的といっても多くのコマンドを打つ必要があります。この面倒を make が担ってくれるのです。

2 つめの例の場合の Makefile は、つぎのようになります。

prog: file1.o file2.o
	gcc -o prog file1.o file2.o

file1.o: file1.c
	gcc -c file1.c

file2.o: file2.c file3.h
	gcc -c file2.c

make を実行すると、つぎのようになります。

$ make
gcc -c file1.c
gcc -c file2.c
gcc -o prog file1.o file2.o

file1.c を書き換えた場合

$ make
gcc -c file1.c
gcc -o prog file1.o file2.o
file3.h を書き換えた場合
$ make
gcc -c file2.c
gcc -o prog file1.o file2.o
適切なファイルだけが選ばれ、効率的にコンパイルが行われているのがわかります。

もちろん、Makefile をつぎのように書くこともできます。

all:
	gcc -o prog file1.c file2.c

この場合、何があろうと (prog が存在していようと) すべてのファイルのコンパイルが実行されます。これでは、コンパイルのコマンドを打ち直す必要がないという利点はありますが、ちょっともったいない。

マクロ

Makefile では、マクロというものが使えます。たとえば

CC = gcc
と書いておけば、別のところで
	$(CC) -c file.c
などとすると、"$(CC)" の部分が "gcc" に置き換えられます。

また、以下のようなものも使えます。

  • $@ : ターゲット
  • $^ : 依存ターゲット
  • $< : 依存ターゲットの先頭
  • $* : ターゲットからサフィックス (接尾辞) を除いたもの
  • $(マクロ名:文字列=文字列2) : マクロの展開の際に文字列を置換する

つぎのような Makefile を考えましょう。

A = a.c b.c c.c

a.o: a.c b.c
	@echo target: $@
	@echo depended: $^
	@echo first depended: $<
	@echo no suffix target: $*
	@echo A.c: $(A)
	@echo A.o: $(A:.c=.o)
	
a.c:
b.c:

これを実行すると、以下のようになります。

$ make
target: a.o
depended: a.c b.c
first depended: a.c
no suffix target: a
A.c: a.c b.c c.c
A.o: a.o b.o c.o

注: $< などに文字列置換を適用する場合は "$(<:.c=.o)" などとする ("$($<:.c=.o)" ではない)。

サフィックスルール

サフィックス (接尾辞) に関して特別な規則を作ることができます。たとえば、C のオブジェクトファイル .o は .c ファイルから同様のコマンドで作られるので、その作り方をまとめて書くことができれば便利です。つぎのように書きます。

.c.o:
	gcc -c $<

こう書いておけば、たとえば

file1.o: file1.c
	gcc -c file1.c

file2.o: file2.c file2.h
	gcc -c file2.c
と書くところを
file2.o: file2.h
と短く書くことができます。

サフィックスは何でもよいわけではなく、make が認識していなければなりません。認識させるには、たとえばつぎのように書きます。

.SUFFIXES: .tex .dvi .ps .java .class

コマンド行

コマンド行では、いくつか特殊な文字が使えます。

  • @コマンド : コマンドを表示せずに実行
  • -コマンド : 失敗しても make を中断しない
  • コマンド 1 ; コマンド 2 : コマンドを 1 行にまとめる

コマンド行は、それぞれの行でそれぞれ別のシェルを立ち上げます。たとえば、ディレクトリに入ってその中身を表示するという場合

all:
	cd aaa
	ls
としても目的を果たせません。"cd aaa" が実行されたあと、別のシェルで "ls" が実行されるため、カレントディレクトリの内容が表示されます。正しくは、つぎのように書きます。
all:
	cd aaa ; ls

インクルード

include で外部ファイルを取り込むことができます。

include ファイル名

ファイル名は "" で囲んだりする必要はありません。

テンプレート

典型的な Makefile は、つぎのようなものになります。

CC = gcc
CFLAGS = -Wall -O2
LDFLAGS =
INCLUDES = -I/usr/local/include
LIBS = -L/usr/local/lib -lm

TARGET = prog
OBJS = file1.o file2.o

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)

clean:
	-rm -f $(OBJS)

.c.o:
	$(CC) $(CFLAGS) $(INCLUDES) -c $<

file1.o: file1.h
file2.o: file2.h

ターゲット clean はお掃除用です。上ではオブジェクトファイルを削除していますが、プログラムも一緒に削除してしまう流儀もあります。