Makefileの書き方
まえがき
その昔、Makefileの書き方という非常にわかりやすいMakefileの入門ページがあったのですが、seiichirou氏の卒業(?)とともにページが消えてしまいました。
私も大変お世話になったページだった、というかこれからもお世話になりそうなので、そこでWebArchiveから引っ張りだしてきたものを、はてな記法で書き直したものがこの記事になります。
元々自分用に書き直したのですが、せっかくなので公開してみます。
なので、以下の著作はseiichirou氏に帰属します*1。
Makefileの書き方
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”を作成するようにしています。
*1:ただ、これって著作権的にOKなのだろうか…誰か詳しい人教えてください。