「Makefileの書き方、その勘どころ」にて：

というわけで続きを書きます。

実は、関数呼び出しを使うときは、代入に「=」を使うより「:=」のほうが適切かつ効率的なときが多いのですが、その話は次の機会にします。

これの説明が中心になります。

内容：

前置き 変数の種類と変数定義 ソースコードの後のほうを参照すること Makeは上から下へと実行していくのだ MakeとLispは似ている 実例

●前置き

以下、Make一般ではなくてGNU Makeの話です。GNU Makeより古いMakeにも備わっていた伝統的機能の説明はしません。

GNU MakeのMakefile記述構文は、ある種のプログラミング言語とみなせます。そのことを認識しないと、シッカリしたMakefileは書けないと思います。そこであえて、Makefile記述構文を「プログラミング言語Make」、あるいは「Make言語」と呼びます。プログラミング言語Makeのソースファイルを、実際の名前がどうであれMakefileと呼びます*1。

最後の実例では、Erlangのビルドに使う処理を出しますが、Erlangの知識は一切不要です。次のことだけ知っておけば十分。

Cで考えたい人は、「.erl→.c」「.hrl→.h」「.beam→.o（または.obj）」と置換すればOKです*2。

●変数の種類と変数定義

GNU Makeの変数は2種類（2つのフレーバー）あって、再帰的変数と単純変数と呼ばれています。でも、再帰的変数と単純変数を構文的に区別することはできません。「=」で定義されるか、「:=」で定義されるかの違いです。

これ以上、Makeの概念と用語法で説明してもラチがあかないと思うので、思い切って次のように考えましょう。

例えば、



INCLUDE_DIR := ../include

HEADERS = $(wildcard $(INCLUDE_DIR)/*.hrl) $(wildcard *.hrl)



var INCLUDE_DIR = "../include";

function HEADERS() {

return wildcard(INCLUDE_DIR + "/*.hrl") + " " + wildcard("*.hrl");

}



(setq INCLUDE_DIR "../include")

(defun HEADERS ()

(concat

(wildcard (concat INCLUDE_DIR "/*.hrl"))

" "

(wildcard "*.hrl")))

となります。 Lisp なら、ですかね。

以前のMakeには、「=」しかなかったので、すべての変数は引数なし関数のような扱いだったのです。習慣と互換性から、変数定義には今でもたいてい「=」が使われていますが、多くの場合は「:=」のほうが適切かつ効率的です。例えば、右辺が定数（$を含まない）なら、「:=」でかまいません（つうか、そのほうがベター）。

●ソースコードの後のほうを参照すること

前節で、JavaScriptとLispによって、Makefileに対応するコードを示しました。JavaScriptとLispでは動作原理が異なるところがあるので、その点に触れておきましょう。



var x = f() + 1;

function f() {

return g() * 2;

}

function g() {

return 3;

}

alert("x=" + x); // OK! x=7

このJavaScriptコードは、問題なく実行できます。JavaScriptはいったんソースファイルを全部見て変数／関数の宣言を処理してから実行に入ります。つまり、次のようにソースを書き換えていると思っていいでしょう。



/* == 宣言部 == */

var x;

function f() {

return g() * 2;

}

function g() {

return 3;

} /* == 実行部 == */

x = f() + 1;

alert("x=" + x);

一方Lispは、上から下へとそのまま実行していくので、まだ定義されてない関数が出現するとエラーになります。



(setq x (+ (f) 1)) ; error 関数fはこの時点で定義されてない.

(defun f () (* (g) 2))

(defun g () 3)

(princ (format "x=%d

" x))

●Makeは上から下へと実行していくのだ

さてMakeですが、JavaScriptよりLispに似た動作をします。



# file: t7.mk

x := $(f) + 1

f = $(g) * 2

g = 3 print_x:

@echo "x='$(x)'"



$ make -f t7.mk

x=' + 1'

Makeでは、すべての変数が空文字列で初期化されている*3のでエラーにはなりませんが、期待した結果ではありません。LispでもMakeでも、変数xへの代入文を最後に持ってくればOKです。



(defun f () (* (g) 2))

(defun g () 3)

(setq x (+ (f) 1))

(princ (format "x=%d

" x))



# file: t8.mk

f = $(g) * 2

g = 3

x := $(f) + 1 print_x:

@echo "x='$(x)'"



$ make -f t8.mk

x='3 * 2 + 1'

あるいは、xを変数ではなくて関数にしてしまうのも手です。関数定義内で未定義関数を使っても平気ですから。



(defun x () (+ (f) 1))

(defun f () (* (g) 2))

(defun g () 3)

(princ (format "x=%d

" x()))



# file: t9.mk

x = $(f) + 1

f = $(g) * 2

g = 3 print_x:

@echo "x='$(x)'"



$ make -f t9.mk

x='3 * 2 + 1'

●MakeとLispは似ている

Make構文の一部は、次のようなプログラミング言語になっています。

データ型は文字列だけ。 文字列を、空白で区切った語（ワード）のリストとして扱うことができる。 変数を持ち、「:=」により代入ができる。 いくつかの組み込み関数を持つ。 引数なしの 関数を「=」で定義できる。（[追記]引数も使えます。コメント欄参照、コッチも参照。[/追記]） 繰り返し制御構造は、組み込み関数foreachでサポートされる。

まー、ちっちゃな関数型言語といってもいいと思いますよ。リスト処理モドキもできるし。雰囲気はLispに似てます；ストールマンの趣味のような気がするな。

でも、ちょっと奇妙なところともありますよ。JavaScriptでもLispでも、変数と引数なし関数はまったくの別物です。しかしMakeでは、変数と引数なし関数の区別は曖昧で、「=」で定義された引数なし関数（Make用語では再帰的変数）が、単なる変数（Make用語では単純変数）に化けたりします。



# file: t10.mk

INCLUDE_DIR := ../include

HEADERS = $(wildcard $(INCLUDE_DIR)/*.hrl)

HEADERS := $(HEADERS) $(wildcard *.hrl) print_headers:

@echo "HEADERS='$(HEADERS)'"

これは何の問題もなく動きます。実はLispでも同じように書けます。



(setq INCLUDE_DIR "../include")

(defun HEADERS ()

(wildcard (concat INCLUDE_DIR "/*.hrl")))

(setq HEADERS (concat (HEADERS) " " (wildcard "*.hrl")))

しかし、変数参照と関数呼び出しの構文が違うので、変数HEADERSと関数HEADERSの区別は厳密にできます。Makeでは、$(HEADERS) という構文しかありません。まー、いいや、許せる範囲。

●実例

「作業ディレクトリにあるすべてのErlangソースを、変数SOURCESにセットする」という問題を考えましょう。

SOURCES:=$(wildcard *.erl) でほぼOKなんだけど、一時ファイルまでSOURCESに入るとイヤですよね。一時ファイル名はtmpで始まるという約束にしておけば、filter-out関数でふるいにかけられます。 SOURCES:=$(filter-out tmp%,$(wildcard *.erl)) ；ここで%は、正規表現「(.*)」に相当するパターン・メタ文字です。

tmpによるネーミング規則から外れるゴミファイルがあるときはどうしましょう。not_sources.mkというファイルに、ゴミを並べておくことにします。



SOURCES:=$(filter-out tmp%,$(wildcard *.erl))

-include not_sources.mk

ifdef NOT_SOURCES

SOURCES:=$(filter-out $(NOT_SOURCES),$(SOURCES))

endif # NOT_SOURCES

もしnot_sources.mkがあれば読み込み、not_sources.mk内で定義されている変数NOT_SOURCESに列挙されたゴミをフィルターアウトします。

さて、作業ディレクトリがゴミばっかりで、ほんとのソースは手で列挙するしか方法がないこともあるでしょう。そのときは、ソースファイル達を露骨に（explicitly :-)）書き並べたsources.mkを作ることにします。



-include sources.mk

ifndef SOURCES

SOURCES:=$(filter-out tmp%,$(wildcard *.erl))

-include not_sources.mk

ifdef NOT_SOURCES

SOURCES:=$(filter-out $(NOT_SOURCES),$(SOURCES))

endif # NOT_SOURCES

endif # !SOURCES

その他、なにやらかにやら設定すると、こんなふうになります。



INCLUDE_DIR:=../include

BIN_DIR:=../ebin -include sources.mk

ifndef SOURCES

SOURCES:=$(filter-out tmp%,$(wildcard *.erl))

-include not_sources.mk

ifdef NOT_SOURCES

SOURCES:=$(filter-out $(NOT_SOURCES),$(SOURCES))

endif # NOT_SOURCES

endif # !SOURCES -include headers.mk

ifndef HEADERS

HEADERS:=$(wildcard $(INCLUDE_DIR)/*.hrl) $(wildcard *.hrl)

endif # !HEADERS COMMON_HEADERS:=$(filter common%,$(HEADERS))

OBJECTS:=$(patsubst %.erl,$(BIN_DIR)/%.beam,$(SOURCES)) print_vars:

@echo "SOURCES='$(SOURCES)'"

@echo "HEADERS='$(HEADERS)'"

@echo "COMMON_HEADERS='$(COMMON_HEADERS)'"

@echo "OBJECTS='$(OBJECTS)'"

さー、あなたもMakeプログラミングしてみませんか*4。