※この記事は続き記事になっています
- 第1回:この記事
- 第2回:Makefileの書き方に関する備忘録 その2 - minus9d's diary
- 第3回:Makefileの書き方に関する備忘録 その3 - minus9d's diary
- 第4回:Makefileの書き方に関する備忘録 その4 - minus9d's diary
makeの使い方を復習しました。今更makeかよと思われそうですが、曖昧に覚えているところも多く、よい勉強になりました。参照したテキストは、オライリーがオープンブックとして公開しているManaging Projects with GNU Make, Third Edition(GNU Make 第三版)。今回の記事はほぼ全部これが出典です。
最初の一歩
makefileに以下の内容を書いて保存。コマンドラインでmakeと単に打つと、このmakefileが実行対象となる。
hello: hello.c gcc hello.c -o hello
この内容は以下のようなルールを意味する。
target: prerequisites command
ここで、
- target:生成されるもの。ここでは実行ファイルhello。
- prerequisites:targetが生成されるために必要なもの。ここではソースコードhello.c。
- command: prerequisitesを使ってtargetを生成するためのシェルコマンド。
makeを実行するとhelloが生成される。makeを再実行した時点で、targetよりもprerequisitesの方が新しければcommandが再実行される。
makefile内に複数のtargetがあるとき、単にmakeとすると一番最初のtargetが生成される。
さまざまなルール
Explicit Rules
先に書いたようなもの。依存関係をベタ書きする。
hello: hello.c sub.c sub.h gcc hello.c sub.c -o hello
ワイルドカード
シェルでよく使う*などがmakefileでも使える。
prog: *.c $(CC) -o $@ $^
$@, $^は自動変数。ここでは$@はprog, $^は*.cを指している。自動変数については後でもう一度出てくる。
Phony Targets
Phonyとは「偽の」という意味。以下のclearのように、ファイルが生成されるわけではないターゲットのことをPhony Targetという。
clean: rm -f *.o lexer.c
cleanというファイルを作りたいわけではなく、cleanという動作をしたいのだ、ということを明示的に表すために、.PHONYを使う。
.PHONY: clean clean: rm -f *.o lexer.c
自動変数
先ほど出てきた$@, $^などを自動変数という。以下のルールを例にとって考える。
hello: hello.c sub.c sub.c sub.c gcc hello.c sub.c -o hello
実行ファイルhelloはhello.c, sub.cに依存している。例のために、prerequisitesの欄にsub.cを何度も書いている。
このとき、自動変数には以下の値が入る。
自動変数* | 意味* | 今回の例での値 | |
$@ | ターゲット | hello | |
$^ | prerequisites、ただし重複分は除く | hello.c sub.c | |
$? | prerequisitesのうちターゲットより新しいもの | 場合による | |
$< | prerequisitesのうち最初に書かれたもの1つ | hello.c | |
$+ | prerequisites、重複分もそのまま | hello.c sub.c sub.c sub.c |
自動変数に入る値を確かめるために、@printfを使ってプリントデバッグすることもできる。(記号の正しいエスケープ方法がわからないので、苦肉の策で半角スペースを挿入している)
hello: hello.c sub.c sub.c sub.c @printf "$$ @ = $@ \n" @printf "$$ ^ = $^ \n" @printf "$$ ? = $? \n" @printf "$$ < = $< \n" @printf "$$ + = $+ \n" gcc hello.c sub.c -o hello
他に$*, $%という自動変数もあるが未調査。
VPATH, vpath
ソースコードの存在する場所を指定するのにVPATHを使う。
VPATH = src
例えば以下のようなディレクトリ構造のとき、
├makefile ├src/ │└main.c └include/ └header.h
makefileに以下のように書ける。
VPATH = src CPPFLAGS = -I include main: main.c $(CC) $(CFLAGS) $^ -o $@
vpathを使うと、VPATHを使うよりも柔軟に場所を指定できる。
vpath %.h include vpath %.c src CPPFLAGS = -I include main: main.c $(CC) $(CPPFLAGS) $^ -o $@
上の例では、.hファイルはincludeフォルダに、.cファイルはsrcフォルダにあることを明示的に表している。ただし、GCCにこの情報は伝わらないので、GCCには-Iでヘッダファイルの場所を教える必要は依然として残る。(参考:c++ - Makefile vpath not working for header files - Stack Overflow)
パターンルール
明示的なルールを書かない場合、自動的に暗黙的なルールが適用される。例えば以下の例では、実行ファイルhelloはhello.oに依存することは表しているが、どうやって実行ファイルhelloを生成するのかについては明示的に表していない。
hello: hello.o hello.o: hello.c
この場合、makeは、makeが組み込みで用意している暗黙的なルールを呼び出す。上の例だと、.oから実行ファイルを生成するのには以下の暗黙的なルールが使われる。
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
また、.cから.oへのコンパイルには以下の暗黙的なルールが使われる。
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@
ちなみに、.ccから.oへのコンパイルには以下の暗黙的なルールが使われる。
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@
CPPFLAGSの「CPP」は、C++のためのフラグという意味ではなく、プリプロセッサのためのフラグという意味。普通CPPFLAGSにはインクルードパスを指定する。C++のためのフラグはCPPFLAGSではなくCXXFLAGSなので間違えないこと。
これらの変数に何を入れればよいか、についてはMakefileの書き方に関する備忘録 その3 - minus9d's diaryで調査した。
暗黙的なルールの一覧は、コマンドラインで
$ make --print-data-base (or -p)
と打てば調べることができる。上で書いた暗黙的なルールも、このコマンドで調べた。
suffix rule
suffix ruleとは、暗黙的なルールを定義するときに使うルールのこと。例は以下。
.c.o: $(COMPILE.c) $(OUTPUT_OPTION) $<
.oファイルは、同じ語幹を持つ.cファイルに依存するという意味。
これは以下のpattern ruleと等価。
%.o: %.c $(COMPILE.c) $(OUTPUT_OPTION) $<
実行ファイルのように拡張子がないファイルに関するsuffix ruleは以下のように書ける。
.p: $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
これは以下のpattern ruleと等価。
%: %.p $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
built-inされているsuffix ruleが悪さをする場合は、空のルールを書いて定義を上書きする。
%.o: %.l %.c: %.l
suffix ruleは古い書き方で、見た目にも分かりにくい(依存関係の順番がpattern ruleと逆になっている)。GNU makeを使う限りにおいては、suffix ruleではなくpattern ruleを使う方がよいらしい。
依存関係の自動生成
C言語の場合、ソースファイルは多数のヘッダファイルに依存するのが普通である。その依存関係を手で保守するのは骨が折れる。そこで依存関係を自動生成することを考える。
例えば以下のようなディレクトリ構造を考える。
├makefile ├src/ │├main.c │├sub1.c │└sub2.c └include/ ├header.h ├sub1.h └sub2.h
ここで各ソースファイルの依存関係は以下のようだとする。
- main.cはheader.h, sub1.h, sub2.hに依存
- sub1.cはheader.h, sub1.hに依存
- sub2.cはsub2.hに依存
結論から言うと、以下のようなMakefileを書けばよい。
vpath %.h include vpath %.c src CPPFLAGS = -I include SOURCES = \ main.c \ sub1.c \ sub2.c # .cを.oに置換。OBJECTS = main.o sub1.o sub2.o となる OBJECTS = $(subst .c,.o,$(SOURCES)) # 実行ファイルmainを生成するルール # 暗黙のルールを利用 main: $(OBJECTS) # .dファイルを読み込む -include $(subst .c,.d,$(SOURCES)) # .cファイルを解析して、.cが依存しているヘッダファイルを.dファイルに書き出す %.d: %.c $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ clean: rm *.exe *.o *.d
このMakefileでは、GCCの-Mオプションがキーポイントになる。GCCで-Mオプションを使用してソースファイルを引数にとると、そのソースファイルがincludeしているヘッダファイルが自動的に列挙される。このMakefileではその機能を利用して、あるソースファイルの依存関係を.dファイルに一時保存する。そして.dファイルをincludeしている。
なお、Makefile中の$$$$というのは、エスケープされて$$、つまりシェルのプロセス番号に置換される。このようにして.d.(プロセス番号)という一時ファイルが作られる。この一時ファイルはsedによって利用された後消去される。
make実行時の便利オプション
- make --just-print (or -n): makeしたときに実行されるコマンドを表示だけする
- make CPPFLAGS=-DDEBUG: makefile内の変数CPPFLAGSを-DDEBUGで上書きする