Makefileなんてもう何年も書いたことがないぞ。ウーン、だめだ、忘れている。
「忘れている」ってよりは、僕の知識じゃ古すぎて、改めて勉強しないとダメでした*1。
なにしろ、makeだけじゃ機能が貧弱なんで、cpp(Cプリプロセッサ)やm4(マクロプロセッサ)と組み合わせて使っていた頃しか知らんからね(古すぎ!)。今じゃGNU Makeを(使おうと思えば)どこでも使えるから、GNU Makeを習えばそれでいいじゃないかな。僕は、Windows上のMSYS(MinGW - Minimal SYStem)でGNU Makeを動かしました。
というわけで、GNU Makeの手習いをしたからメモしておきます。以下、名前がMakefileじゃなくても、GNU Makeへの指示を書いたファイルは何でもMakefileと呼びます。
[追記]id:paellaさんのブックマークコメントに曰く:
GNU Makeの初心者向け資料。というよりも、Makeの一般的なルールを知っている人が読むと理解が深まる情報。
はい、そういうことです。ターゲットとかルールについては、別な資料をあたってください。
[/追記]
内容:
- includeとif
- 変数と関数
- 空白に注意!
- 続きがあると思う
●includeとif
GNU Makeは、cppのお世話になんかなりませんね。includeとifがあります。if文は、ifeq, ifneq, ifdef, ifndefで、文字列の等値性と変数の定義状況を調べることができます。等号以外の比較演算/論理演算は使えないから、その点ではcppのほうが高機能((cppだと、#if (defined(debug) || defined(DEBUG)) && !defined(NO_DEBUG)
のような書き方ができます。))かもしれないけど、これだけでも間に合うでしょう。
Makefile実例:
# file: t1.mk
include ../common.mkifeq ($(OS_TYPE),win32)
include ../win32.mk
endif-include optional.mk
ifdef debug
DEBUG_FLAG = -DDEBUG
else
DEBUG_FLAG =
endifprint_vars:
@echo "OS_TYPE='$(OS_TYPE)'"
@echo "DEBUG_FLAG='$(DEBUG_FLAG)'"
念のため説明:
- ファイル../common.mkをインクルードします。僕は、
include "../common.mk"
と書いてハマってました。cppじゃないって。 - 変数OS_TYPEの値がwin32のときは、ファイル../win32.mkをインクルードします。変数の値は、環境変数やmakeコマンド起動時オプションにより指定できます。
- includeディレクティブの前にマイナスを付けると、ファイルがなくてもエラーになりません。
-include optional.mk
は、ファイルoptional.mkがあれば読み込み、なければ何もしません。 - 変数debugが定義されていれば(値はなんでもいい)、変数DEBUG_FLAGの値を-DDEBUGに設定します。そうでないなら、DEBUG_FLAGの値は空(長さ0の文字列、未定義と同じ)です。
- 確認のためのターゲットprint_varsが実行されると、変数値を表示します。
実行例:
$ make -f t1.mk
OS_TYPE=''
DEBUG_FLAG=''$ make OS_TYPE=win32 -f t1.mk
OS_TYPE='win32'
DEBUG_FLAG=''$ make OS_TYPE=win32 debug=1 -f t1.mk
OS_TYPE='win32'
DEBUG_FLAG='-DDEBUG'
●変数と関数
Makefileの構文はどうも一貫性がなく、オマジナイのような記号が色々あって、なじみにくいものでした。GNU Makeは、構文と意味論を整理して合理化しようと試みています。互換性から古い構文も残っていますが、新しい一貫した構文を使ったほうがいいと思います。
構文の大原則として、変数参照は$(FOO)のようにドル記号(「$」)を使います。丸括弧(例:$(FOO))または波括弧(例:${FOO})は必須だと思ってください。例外は変数名が1文字のときで、$FOOは$(F)OOと同じです。$@、$< のような変な記号も、名前が「@」、「<」である変数の参照です。したがって、$(@), $(<)と書いてもかまいません。
ドル記号は、変数参照だけでなく、関数呼び出しにも使えます。例えば、$(subst ge,GA,$(FOO)) は関数substに3つの引数を渡して呼び出しています。substは文字列置換関数で、$(FOO)の値である文字列に出現するgeをGAに置き換えます。
# file: t2.mk
FOO=hoge hoge fuga hage
BAR=$(subst ge,GA,$(FOO))print_vars:
@echo "FOO='$(FOO)'"
@echo "BAR='$(BAR)'"
このMakefileを実行すると、
$ make -f t2.mk
FOO='hoge hoge fuga hage'
BAR='hoGA hoGA fuga haGA'
Perlなら、次ようになりますかね。
$FOO = "hoge hoge fuga hage";
($BAR = $FOO) =~ s/ge/GA/g;
print "FOO='$FOO'\n";
print "BAR='$BAR'\n";
実は、関数呼び出しを使うときは、代入に「=」を使うより「:=」のほうが適切かつ効率的なときが多いのですが、その話は次の機会にします。(続きに書きました。)
もちろん、ドル記号を使った変数参照/関数呼び出しの出現は、その値/戻り値に展開(置換)されます。ドル記号そのものを書きたいなら$$とします。
●空白に注意!
Makefileの変数には、値として文字列が入ります。しかし、プログラミング言語とは違って、引用符(「"」や「'」)をまったく使わないので、文字列の開始/終了が分かりにくいですねー。
次のMakefileを実行するとどうなるか、予測できますか?
# file: t3.mk
X1=a
X2= a
X2 = a
X3= a # with trailing spaces
X4= a b
X5= a bprint_vars:
@echo "X1='$(X1)'"
@echo "X2='$(X2)'"
@echo "X3='$(X3)'"
@echo "X4='$(X4)'"
@echo "X5='$(X5)'"
やってみましょう。
イコールの左右の空白は無視されます。したがって、値の先頭空白が無視されることになります。しかし、値の末尾や中間にある空白はそのまま残ります。
$ make -f t3.mk
X1='a'
X2='a'
X3='a '
X4='a b'
X5='a b'
次はどうでしょう?
# file: t4.mk
X1= hello world
X2=$(subst h,H,$(X1))
X3=$(subst h,H,$(X1))
X4=$(subst h, H,$(X1))
X5=$(subst h, H ,$(X1))
X6=$(subst h, H, $(X1))
X7=$(subst h ,H ,$(X1))print_vars:
@echo "X1='$(X1)'"
@echo "X2='$(X2)'"
@echo "X3='$(X3)'"
@echo "X4='$(X4)'"
@echo "X5='$(X5)'"
@echo "X6='$(X6)'"
@echo "X7='$(X7)'"
こうなります。
$ make -f t4.mk
X1='hello world'
X2='Hello world'
X3='Hello world'
X4=' Hello world'
X5=' H ello world'
X6=' Hello world'
X7='hello world'
関数呼び出しにおける、関数名と引数並びのあいだの空白はいくつあっても同じですが、カンマの前後の空白とお尻の空白はそのまま保存されます。
ifeqはもっと奇妙な挙動をします。「余分な空白があるとヤバいな」と思ったら、前後の空白を削るstrip関数を使ってください。なお、strip関数は中間の空白も圧縮します*2。
# file: t5.mk
A=foo
B=foo # with trailing spaceifeq ($(A),$(A))
X1 = A=A
endififeq ($(A), $(A))
X2 = A=_A
endififeq ( $(A),$(A))
X3 = _A=A
endififeq ($(A),$(A) )
X4 = A=A_
endififeq ( $(A), $(A))
X5 = _A=_A
endififeq ($(A),$(B))
X6 = A=B
endififeq ($(B),$(A) )
X7 = B=A_
endififeq ($(A),$(strip $(B)))
X8 = A=stripB
endifprint_vars:
@echo "X1='$(X1)'"
@echo "X2='$(X2)'"
@echo "X3='$(X3)'"
@echo "X4='$(X4)'"
@echo "X5='$(X5)'"
@echo "X6='$(X6)'"
@echo "X7='$(X7)'"
@echo "X8='$(X8)'"
実行結果はこんなです。
$ make -f t5.mk
X1='A=A'
X2='A=_A'
X3=''
X4=''
X5=''
X6=''
X7='B=A_'
X8='A=stripB'
X2の'A=_A'ってのがなんか不思議ですねー、どうやらifeqではカンマの左右の空白は削除されるようです(苦笑)。ともかくトラブルを未然に防ぐには、
- 余分な空白を入れない!
のが一番です。
では、空白から始まる変数値はどうやって設定するのでしょうか? 次の裏技があります。(これも「=」より「:=」を使うのが望ましい。)
# file: t6.mk
EMPTY=
SPACE=$(EMPTY) $(EMPTY)print_vars:
@echo "EMPTY='$(EMPTY)'"
@echo "SPACE='$(SPACE)'"
$ make -f t6.mk
EMPTY=''
SPACE=' '
SPACEを定義するときの二番目の$(EMPTY)は不要ですが、書いてないと分かりにくいですよね。
●続きがあると思う
まだ、関数を使ってソースやターゲットを生成する方法とかパターン規則の説明をしてないので、続きを書くと思います。調べているうちに、GNU Makeの構文(の一部)はある種のプログラミング言語だという気がしてきました;そのことも書きたい気がしてます。いつものことながら、保証はしませんけどね。
[追記]続きを書きました。
[/追記]