2012年7月2日月曜日

make の動きを解剖する

make では ファイル間の依存関係を精査し、必要なオブジェクトのみを再構築するのが基本である。
ただし、PHONYターゲットのように、必ずしもファイル実体を伴わないルールもある。
それらの依存関係が複雑にからんだ場合、それぞれの構築ルールを実行すべきか否か、makeはどのように決定しているのかだろうか。

私は make 内部実装はいっさい見ていないが、いくつかの実験を行い、内部の動きを推論することにする。


(実験1)
次の内容の Makefile を用意。

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c



ファイル a とファイル b の間の関係としては、次の5通りがある。
それぞれ make を実行したときの表示結果を示す。

(1) ab も存在しない → 実行結果 cba
(2) a だけが存在 → 実行結果 cba
(3) b だけが存在 → 実行結果 cba
(4) ab も存在し、 a の方が新しい → 実行結果 cb
(5) ab も存在し、 b の方が新しい → 実行結果 cba


この結果から推論できること:
(推論1)

a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「ファイル a もファイル b も存在し、かつ a の方が新しい」(これを以下、条件1と呼ぶ)が成立すると、構築ルールを実行しない。
条件1が成立しないときは構築ルールを実行する。


では、条件1が成り立つかどうか評価するのは、どの時点なのだろうか?


(実験2)
次のMakefile を用意する。

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch b

c:
    @echo -n c


条件1が成り立つ状態にしてから、このmakefileを実行してみる。
$ touch b
$ touch a
$ make
cba


(推論2)
この実験結果から、条件1が成り立つかどうか評価しているのは、 b の構築が終わったあとだと考えられる。
なぜならば、make を開始した時点では、条件1が成立している。
@touch b」を行った後で、条件1が成立しなくなる。
@echo -n a」が実行されているという結果を見れば、 ab の日付比較は直前に行われているはず。
これはmakeの動きとしては当然である。
依存関係ツリーの下位から順番に構築している中で、更新されるファイルがあるはずだからだ。


しかし、事態はそう簡単ではない。
推論2を否定する実験結果を示そう。

(実験3)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch b
    @touch a

c:
    @echo -n c



を実行してみる。

$ make
cba

a: b
    @echo -n a

の直前では、条件1が成立しているはずなのに「@echo -n a」が実行されている。
残念ながら、(推論2)は間違っているようだ。



では、(実験1)〜(実験3)をすべて説明できる推論として次を考える。

(推論3)
a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「make開始時点のファイル a と、このルールを実行する直前のファイル b を比較する。両者とも存在し、 a の方が新しい」(これを以下、条件2と呼ぶ)が成立すると、構築ルールを実行しない。
それ以外の時は、構築ルールを実行する。

さて、この推論が正しいかどうか実験してみる。


(実験4)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch -t 201207021200 b
    @touch -t 201207021300 a

c:
    @echo -n c



$ touch -t 201207021159 a
$ make
cba
$ rm b
$ touch -t 201207021201 a
$ make
cb


make を実行前のファイルaの時刻が11時59分なら、条件2が成立しないので、「@echo a」を実行している。
一方、ファイルaの時刻が12時01分なら、条件2が成立するので、「@echo a」を実行しない。
Makefileの中で、ファイルaの時刻を13時に更新しているが、これは見ていない。
これは(推論3)を支持する結果である。


(実験5)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c
    @rm a



$ touch b
$ touch a
$ make
cb

途中で、ファイル a は消されているにもかかわらず、「@echo -n a」は実行されていない。
make開始時点で a が存在しているかどうかしか見ていないから。



実験5を書きかえて、以下のようにしてみる。

(実験6)

all: clean a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c

clean:
    rm -f a


$ touch b
$ touch a
$ make
rm -f a
cba


これは(推論3)が間違っていることを示す結果です。

(実験7)

all: touch a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c

touch:
    touch b
    touch a


$ make
touch b
touch a
cb


実験6, 実験7も説明できる推論として以下を考える。

(推論4)
a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「この依存関係ツリーを処理する直前のファイル a と、このルールを実行する直前のファイル b を比較する。両者とも存在し、 a の方が新しい」が成立すると、構築ルールを実行しない。
それ以外の時は、構築ルールを実行する。


いろいろ実検してたが、makeが構築ルールを実行するかどうかの判断は結構複雑のようだ。
実装コードを見ずに、実検してみた限りでは、(推論4)のようになっていると思うのだが。。

0 件のコメント:

コメントを投稿