このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Makefileの書き方、その勘どころ

「ほとんど忘れた、Makefile」 にて:

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の一般的なルールを知っている人が読むと理解が深まる情報。

はい、そういうことです。ターゲットとかルールについては、別な資料をあたってください。
[/追記]

内容:

  1. includeとif
  2. 変数と関数
  3. 空白に注意!
  4. 続きがあると思う

●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.mk

ifeq ($(OS_TYPE),win32)
include ../win32.mk
endif

-include optional.mk

ifdef debug
DEBUG_FLAG = -DDEBUG
else
DEBUG_FLAG =
endif

print_vars:
@echo "OS_TYPE='$(OS_TYPE)'"
@echo "DEBUG_FLAG='$(DEBUG_FLAG)'"

念のため説明:

  1. ファイル../common.mkをインクルードします。僕は、include "../common.mk"と書いてハマってました。cppじゃないって。
  2. 変数OS_TYPEの値がwin32のときは、ファイル../win32.mkをインクルードします。変数の値は、環境変数やmakeコマンド起動時オプションにより指定できます。
  3. includeディレクティブの前にマイナスを付けると、ファイルがなくてもエラーになりません。-include optional.mkは、ファイルoptional.mkがあれば読み込み、なければ何もしません。
  4. 変数debugが定義されていれば(値はなんでもいい)、変数DEBUG_FLAGの値を-DDEBUGに設定します。そうでないなら、DEBUG_FLAGの値は空(長さ0の文字列、未定義と同じ)です。
  5. 確認のためのターゲット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 b

print_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 space

ifeq ($(A),$(A))
X1 = A=A
endif

ifeq ($(A), $(A))
X2 = A=_A
endif

ifeq ( $(A),$(A))
X3 = _A=A
endif

ifeq ($(A),$(A) )
X4 = A=A_
endif

ifeq ( $(A), $(A))
X5 = _A=_A
endif

ifeq ($(A),$(B))
X6 = A=B
endif

ifeq ($(B),$(A) )
X7 = B=A_
endif

ifeq ($(A),$(strip $(B)))
X8 = A=stripB
endif

print_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の構文(の一部)はある種のプログラミング言語だという気がしてきました;そのことも書きたい気がしてます。いつものことながら、保証はしませんけどね。

[追記]続きを書きました。

[/追記]

*1:Makefileなんて一生書かないと思っていたんだけどね。

*2:[追記]最初、「要注意」と書いたけど、中間の空白が圧縮されて困ることはあまりないですね。[/追記]