読者です 読者をやめる 読者になる 読者になる

Makefileの書き方

まえがき

その昔、Makefileの書き方という非常にわかりやすいMakefileの入門ページがあったのですが、seiichirou氏の卒業(?)とともにページが消えてしまいました。

私も大変お世話になったページだった、というかこれからもお世話になりそうなので、そこでWebArchiveから引っ張りだしてきたものを、はてな記法で書き直したものがこの記事になります。
元々自分用に書き直したのですが、せっかくなので公開してみます。

なので、以下の著作はseiichirou氏に帰属します*1

Makefileの書き方

はじめに

ここでは、Makefileの中でもGNU makeにかぎって説明します。

makeって何?

ソースファイルを分割して大規模なプログラムを作成していると、コマンドでコンパイルするのが面倒です。また、一部のソースファイルを書き換えただけなのに全部をコンパイルし直すのは時間の無駄です。

そんな問題を解決するのがmakeです。Makefileと呼ばれるテキストファイルに必要なファイルと各ファイルのコンパイルのコマンド、ファイル間の依存関係を記します。そして、“make”というコマンドを実行するだけで、自動的にコマンドを実行してコンパイルしてくれます。これだけではスクリプトと大差がないのですが、makeはMakefileに記された依存関係に基づいて更新されたファイルの内関連のあるものだけを更新することで、コンパイル時間を短くします。

makeは特定のプログラミング言語に依存したものではありません。C言語のソースファイルのコンパイルにも使えますし、Verilog-HDLのシミュレーションにも使えますし、TeXのコンパイルにも使えます。

make色々

実はmakeには色々種類があります。主なものをあげると以下の通りです。

“nmake”はMicrosoftのVisual C++に搭載されているmakeです。Windows用です。ただし、Visual C++にはプロジェクト管理機能があるのでnamkeのお世話になることはほぼないでしょう。お世話になるのは、公開されているソースファイルをコンパイルする場合だけでしょう。

Borlandの“make”はBorland C++ Compilerなどに入ってます。Windows用です。Borland C++ Compilerがフリーとなったことで、公開されているソースファイルに意外とBorland make用のMakefileがついていたりします。

GNUの“make”はUNIX系では標準的なmakeです。Windows用のGNU makeには、Cygwinに付属のものか、MinGWのものがあります。ここでは、GNU makeの使い方を説明します。

Solarisの“make”はSolarisに付属するmakeです。Solaris用のソフトの中にはGNU makeではうまくいかないものがあるそうで、そんなときにSolaris makeを使うそうです。Solaris makeをはじめとする、Solaris付属の開発環境は、/usr/ccs/bin にあります。

makeでHello World!

それではメイクファイルを書いてみましょう。ここではソースファイルとしてコンパイル方法でも使ったC言語Hello World!を使用します。

/* hello.c */
#include <stdio.h>

int main(int argc, char* argv[]) {
  printf("Hello World!\r\n");
  return 0;
}

同じディレクトリに“Makefile”というファイルを作成して、以下の内容を記述してください。

# Makefile
hello: hello.c
	gcc -o hello hello.c

三行目の先頭は空白文字ではなくてタブ文字(Tab)なので注意してください! 一行目はコメントです。“#”と書くとその行の“#”以降の文字列はコメントとなります。コマンドからmakeを実行すると以下のようにコンパイルしてくれます。

$ make
gcc -o hello hello.c

このあともう一度makeを実行してみましょう。

$ make
make: `hello' は更新済みです

hello.cが更新されていない(helloの方がhello.cよりも日付が新しい)のでコンパイルされません。こんな感じで、必要な部分のみコンパイルしてくれます。

Makefileのファイル名を指定する場合は

$ make -f Makefile

とします。ファイル名を指定しない場合は、“GNUmakefile”、“makefile”、“Makefile”の順に検索します。

Makefileの基本文法: 依存関係行

Makefileの基本的な構文は依存関係を表す依存関係行です。依存関係行はこんな感じです。

ターゲット名: 依存ファイル名1 依存ファイル名2 依存ファイル名3
	コマンド行1
	コマンド行2
	コマンド行3

ターゲット名は一般的に生成されるファイルのファイル名にします(そうでない場合については後述します)。

ターゲット名の後に“:”を書いて、その後にスペース区切りで依存するファイルのファイル名を記述します。これらのファイルのうちどれか一つでも更新されるとコマンドが実行されます。

ターゲット名を指定してmakeを実行する場合は

$ make ターゲット名

とします。ターゲット名を省略すると、Mkefileの中で先頭のターゲットが実行されます。

ターゲット名から始まる行の次の行から実行するコマンドを記述します。コマンドを記述する場合は必ず先頭にタブ文字を入れる必要があります。

例として、C言語の分割コンパイルをしてみましょう。分割コンパイル用に以下のファイルを用意します。

/* hello.c */
#include <stdio.h>

void edajima(void);

int main(int argc, char* argv[]) {
  edajima();
  return 0;
}
/* edajima.c */
#include <stdio.h>

void edajima(void);

void edajima(void) {
  printf("わしが男塾塾長 江田島平八である!!\r\n");
}

メイクファイルはこんな感じです。

# Makefile
hello: hello.c edajima.c
	gcc -o hello hello.c edajima.c

hello.cまたはedajami.cのいずれかを更新するとコンパイルし直してくれます。しかし、このままでは更新されてないファイルもコンパイルし直されてしまうので、少し変更します。

# Makefile
hello: hello.o edajima.o
	gcc -o hello hello.o edajima.o

hello.o: hello.c
	gcc -c hello.c

edajima.o: edajima.c
	gcc -c edajima.c

こうしてmakeを実行すると、

$ make
gcc -c hello.c
gcc -c edajima.c
gcc -o hello hello.o edajima.o

となります。ここで、edajima.cを書き換えます。

/* edajima.c */
#include <stdio.h>

void edajima(void);

void edajima(void) {
  printf("わしが男塾塾長 江田島平八である!!\r\n");
  printf("以上!\r\n");
}

そしてmakeを実行すると、

$ make
gcc -c edajima.c
gcc -o hello hello.o edajima.o

となって、edajima.oだけが更新されます。

依存関係行の応用その一

依存関係行を使った応用について説明します。プログラムをコンパイルすると中間ファイルなどができていちいち削除するのが面倒です。そこで、Makefileに以下の行をつけたします。

# Makefile
hello: hello.o edajima.o
	gcc -o hello hello.o edajima.o

hello.o: hello.c
	gcc -c hello.c

edajima.o: edajima.c
	gcc -c edajima.c

clean:
	rm -f hello hello.o edajima.o

こうしてコマンドで以下のように実行します。

$ make clean
rm -f hello hello.o edajima.o

不要なファイルをすべて削除してくれます。“clean”は依存するファイルがなく、cleanというファイルを生成するわけでもなく、コマンドを実行するだけです。このようなターゲットのことを“phony target”と呼びます。phonyターゲットを使用する場合、ターゲット名と同じ名前のファイルがあると変なことになります。

$ touch clean
$ make clean
make: `clean' は更新済みです

これをさけるためにはMakefileを以下のように書き換えます。

.PHONY: clean
clean:
	rm -f hello hello.o edajima.o

こうするとcleanというファイルが存在していても問題ありません。

依存関係行の応用その二

もう一つの応用は、複数のプログラムを作成するときに役に立ちます。ここでは以下のソースファイルを追加します。

/* raiden.c */
#include <stdio.h>

int main(int argc, char* argv) {
  printf("男の勝負に言葉はいらん\r\n");
  printf("ただそれだけのこと…………!!\r\n");
  return 0;
}

そしてMakefileを以下のようにします。

# Makefile
hello: hello.o edajima.o
	gcc -o hello hello.o edajima.o

hello.o: hello.c
	gcc -c hello.c

edajima.o: edajima.c
	gcc -c edajima.c

raiden: raiden.o
	gcc -o raiden raiden.o

raiden.o: raiden.c
	gcc -c raiden.c

.PHONY: clean
clean:
	rm -f hello hello.o edajima.o raiden raiden.o

これでhelloとraidenを作ろうとすると、

$ make hello
$ make raiden

となり、面倒です。そこで、ダミーの依存関係行を追加します。

# Makefile
.PHONY: all
all: hello raiden

hello: hello.o edajima.o
	gcc -o hello hello.o edajima.o

hello.o: hello.c
	gcc -c hello.c

edajima.o: edajima.c
	gcc -c edajima.c

raiden: raiden.o
	gcc -o raiden raiden.o

raiden.o: raiden.c
	gcc -c raiden.c

.PHONY: clean
clean:
	rm -f hello hello.o edajima.o raiden raiden.o

先頭に追加した“all”がミソです。これでmakeを実行すると、

$ make
gcc -c hello.c
gcc -c edajima.c
gcc -o hello hello.o edajima.o
gcc -c raiden.c
gcc -o raiden raiden.o

となってめでたく二つのプログラムを一度に作成することができました。

依存関係行の応用その三

C言語ではコンパイルしないけどソースファイルにインクルードされるヘッダーファイルが存在します。ヘッダーファイルが更新されたときにソースファイルをコンパイルし直すにはどうしたらよいのでしょうか?

この問題を解決するには、同じターゲット名の依存関係行を追加します。例えば以下のようなファイルを用意します。

/* jaki.h */
char serifu[] = {"聖紆麈 貴様の命 この邪鬼とともにある"};
/* jaki.c */
#include <stdio.h>
#include "jaki.h"

int main(int argc, char* argv[]) {
  printf("%s\r\n", serifu);
  return 0;
}
# Makefile
.PHONY: all
all: jaki

jaki: jaki.o
	gcc -o jaki jaki.o

jaki.o: jaki.c
	gcc -c jaki.c

.PHONY: clean
clean:
	rm -rf jaki jaki.o

makeを実行すると

$ make
gcc -c jaki.c
gcc -o jaki jaki.o

となります。ここで“jaki.h”を書き換えます。

/* jaki.h */
char serifu[] = {"す すまん 貴様等の期待と信頼を裏切った…………"};

そして、makeを実行すると

$ make
make: `all' に対して行うべき事はありません。

といって更新してくれません。そこで、以下のように“Makefile”を書き換えます。

# Makefile
.PHONY: all
all: jaki

jaki: jaki.o
	gcc -o jaki jaki.o

jaki.o: jaki.c
	gcc -c jaki.c

jaki.o: jaki.h

.PHONY: clean
clean:
	rm -rf jaki jaki.o

“jaki.o: jaki.h”という行がポイントです。そして、makeを実行すると

$ make
gcc -c jaki.c
gcc -o jaki jaki.o

となり、ちゃんと更新されます。

マクロ

ここから少し難しくなります。これまではMakefieにファイル名やコマンド名を直接書いていました。しかし、マクロを使うと直接書かなくてすみ、他への流用などが容易となります。マクロを定義するには以下のようにします。

マクロ名 = 文字列

マクロを参照するには、

$(マクロ名)

または

${マクロ名}

とします。実際に使ってみるとこんな感じです。

# Makefile
objs = hello.o edajima.o

hello: $(objs)
	gcc -o hello $(objs)

hello.o: hello.c
	gcc -c hello.c

edajima.o: edajima.c
	gcc -c edajima.c

.PHONY: clean
clean:
	rm -f hello $(objs)

ここでは、オブジェクトファイル名を“objs”というマクロとして定義しています。 “$(objs)”は“hello.o edajima.o”に置換されます。

GNU makeでは、定義済マクロとして以下のものがあります。

マクロ名 文字列 説明
AR ar アーカイブユーティリティ
AS as アセンブラ
CC cc Cコンパイラ
CXX g++ C++コンパイラ
CO co RCS ファイルからリビジョンをチェックアウトする
CPP $(CC) -E Cプリプロセッサ
FC f77 Fortranコンパイラ
GET get 知らね
LEX lex lex
PC pc Pascalコンパイラ
YACC yacc yacc
YACCR yacc -r 知らね
MAKEINFO makeinfo Texinfo -> Info
TEX tex TeX
TEXI2DVI texi2dvi Texinfo -> DVI
WEAVE weave 知らね
CWEAVE cweave 知らね
TANGLE tangle 知らね
CTANGLE ctangle 知らね
RM rm -f ファイルの削除

上記のプログラムの引数用のマクロもあります。

マクロ名 文字列 説明
ARFLAGS rv ARの引数
ASFLAGS ASの引数
CFLAGS CCの引数
CXXFLAGS CXXの引数
COFLAGS COの引数
CPPFLAGS CPPの引数
FFLAGS FCの引数
GFLAGS GETの引数
LDFLAGS リンカldの引数
LFLAGS LEXの引数
PFLAGS PCの引数
RFLAGS 知らね
YFLAGS YACCの引数

これらのマクロは再定義可能です。例えば、こんな感じです。

# Makefile
objs = hello.o edajima.o
CC = gcc

hello: $(objs)
	$(CC) -o hello $(objs)

hello.o: hello.c
	$(CC) -c hello.c

edajima.o: edajima.c
	$(CC) -c edajima.c

.PHONY: clean
clean:
	$(RM) hello $(objs)

ここでは“CC”というマクロを“gcc”という文字列で再定義しています。また、“RM”というマクロをそのまま使用しています。

内部マクロ

前述のマクロは単純に文字列に置換するだけでしたが、内部マクロはもう少し複雑になります。例えば、こんな感じの内部マクロがあります。

hello: $(objs)
	$(CC) -o $@ $(objs)

ここでは“$@”という内部マクロを使用しています。これはターゲット名を表すものです。そのため上記の記述は、

hello: $(objs)
	$(CC) -o hello $(objs)

と解釈されます。

また、以下のものもあります。

hello.o: hello.c
	$(CC) -c $<

ここでは“$<”という内部マクロを使用しています。これは依存ファイルの先頭のファイル名を表すものです。そのため上記の記述は、

hello.o: hello.c
	$(CC) -c hello.c

と解釈されます。依存ファイル名のリストを表す“$^”という内部マクロもあります。

内部マクロをまとめるとこんな感じです。

内部マクロ名 説明
$@ ターゲット名
$% ターゲットメンバ名(ターゲット名が“edajima.a(momo.o)”の場合、$@は“edajima.a”で、$%は“momo.o”
$ 依存ファイルの先頭のファイル名
$? 依存ファイルの内、ターゲットより新しいファイルのリスト
$^ 依存ファイルのリスト
$+ わかんね
$* わかんね

マクロと内部マクロを駆使すると、Makefileはこんな感じになります。

# Makefile
program = hello
objs = hello.o edajima.o
CC = gcc
CFLAGS = -g -Wall

$(program): $(objs)
	$(CC) -o $(program) $^

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

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

.PHONY: clean
clean:
	$(RM) $(program) $(objs)

サフィックスルール

サフィックスルールとは、ファイル名の拡張子(サフィックス)ごとにルールを定義するものです。例えばこんな感じです。

.SUFFIXES: .o .c

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

“.SUFFIXES”は依存関係行と同じ形ですが、意味が違います。サフィックスルールを適用する拡張子のリストを書きます。

“.c.o”がサフィックスルールとなっており、拡張子が“.o”のファイルは拡張子を“.c”変えたファイルに依存していることを表します。変換方法はコマンドで表されています。例えば、ターゲット名が“hoge.o”ならばmakeはこのサフィックスルールより“hoge.c”に依存していると判断して、コマンドを実行し“hoge.o”を生成します。

サフィックスルールを用いると、こんな感じで書けます。

# Makefile

# プログラム名とオブジェクトファイル名
program = hello
objs = hello.o edajima.o

# 定義済マクロの再定義
CC = gcc
CFLAGS = -g -Wall

# サフィックスルール適用対象の拡張子の定義
.SUFFIXES: .c .o

# プライマリターゲット
$(program): $(objs)
	$(CC) -o $(program) $^

# サフィックスルール
.c.o:
	$(CC) $(CFLAGS) -c $<

# ファイル削除用ターゲット
.PHONY: clean
clean:
	$(RM) $(program) $(objs)

ここまでくると、あとは“program”や“objs”を書き換えるだけでいくらでも流用ができます。ちなみに、ヘッダーファイルの依存関係だけは自分で記述しなければなりません。例えばこんな感じです。

# Makefile

# プログラム名とオブジェクトファイル名
program = jaki
objs = jaki.o

# 定義済マクロの再定義
CC = gcc
CFLAGS = -g -Wall

# サフィックスルール適用対象の拡張子の定義
.SUFFIXES: .c .o

# プライマリターゲット
$(program): $(objs)
	$(CC) -o $(program) $^

# サフィックスルール
.c.o:
	$(CC) $(CFLAGS) -c $<

# ファイル削除用ターゲット
.PHONY: clean
clean:
	$(RM) $(program) $(objs)

# ヘッダーファイルの依存関係
jaki.o: jaki.h

さらなる応用

分割Makefile

プログラムが複雑になって、ディレクトリごとにソースコードを分けるなどしていくと、一つのMakefileで管理するのは面倒になってきます。そんな時には、Makefileを分割することができます。例えば、subdirというサブディレクトリの中に別のMakefileがあるとした場合、カレントディレクトリのMakefile

subsystem:
	cd subdir && $(MAKE)

または

subsystem:
	$(MAKE) -C subdir

とします。

C言語のヘッダーファイルの依存関係の自動解決

C言語でプログラミングしている際に、ソースファイルが増えるとヘッダファイルの依存関係をいちいち記述するのは面倒です。色々な解決方法があるみたいですが、とりあえずこんなん考えてみました。

# Makefile

# プログラム名とオブジェクトファイル名
program = jaki
objs = jaki.o

# 定義済マクロの再定義
CC = gcc
CFLAGS = -g -Wall

# サフィックスルール適用対象の拡張子の定義
.SUFFIXES: .c .o

# プライマリターゲット
.PHONY: all
all: depend $(program)

# プログラムの生成ルール
$(program): $(objs)
	$(CC) -o $(program) $^

# サフィックスルール
.c.o:
	$(CC) $(CFLAGS) -c $<

# ファイル削除用ターゲット
.PHONY: clean
clean:
	$(RM) $(program) $(objs) depend.inc

# ヘッダーファイルの依存関係
.PHONY: depend
depend: $(objs:.o=.c)
	-@ $(RM) depend.inc
	-@ for i in $^; do\
		cpp -MM $$i | sed "s/\ [_a-zA-Z0-9][_a-zA-Z0-9]*\.c//g" >> depend.inc;\
	done

-include depend.inc

gccプリプロセッサであるcppとsedを組み合わせています。cppは指定したソースファイルの依存関係をmakeの形式で出力してくれるオプションを持っています。それを使って、全ソースファイルの依存関係を“depend.inc”に出力して、それをインクルードしています。“make depend”とコマンドを実行すればOKです。また、“all: depend $(program)”とすることで、makeする際に毎回“depend.inc”を作成するようにしています。

その他

GNU makeには他にも色々な機能があります。詳しくはWebのマニュアルを見てください。また、makeを発展させた、autoconf、automake、libtool、などもあります。これはOS間の差異を吸収するためのツールです。

*1:ただ、これって著作権的にOKなのだろうか…誰か詳しい人教えてください。

広告を非表示にする