makeなんてよく使うものだから分かっているつもりだったけど実はよく分かっていなかったのが、変数展開がどのタイミングで行われるかということ。
Makefileでの :=
は simply expanded variable といって一度しか展開されないが、 =
は参照するたびに展開される。
DATE = $(shell date) .PHONY: all all: @echo $(DATE) @$(shell sleep 3) @echo $(DATE) @$(shell sleep 3) @echo $(DATE)
これは、 $(DATE)
を参照するたびに展開されるから、値はどんどん変わっていく。
Thu Apr 4 20:13:21 JST 2019 Thu Apr 4 20:13:24 JST 2019 Thu Apr 4 20:13:27 JST 2019
ここまでは知っていたのだけど、どのタイミングで展開されるのかというのを知らなくて、次のようなケースで悩んでしまった。
FILE := /tmp/example.txt $(shell echo 'Old contents' > $(FILE)) CONTENTS = $(shell cat $(FILE)) .PHONY: all all: echo $(CONTENTS) echo 'New contents' > $(FILE) echo $(CONTENTS)
ファイルに書き込んだ後に $(CONTENTS)
を参照しているから、最後の行は New contents
と表示する… わけではない。
echo Old contents Old contents echo 'New contents' > /tmp/example.txt echo Old contents Old contents
こうなる理由だが、Makefileの $(...)
の展開はレシピを実行する前に行われるからだ。上の例だと、レシピ内のすべての $(CONTENTS)
や $(FILE)
が展開されてから、3つのコマンド実行が行われる。
次の例を考えてみよう。
FILE := /tmp/sample.txt .PHONY: all all: @echo $(shell date) @$(shell sleep 3) @date > $(FILE) @$(shell sleep 3) @echo $(shell date) @$(shell sleep 3) @cat $(FILE)
レシピ3行目の date > $(FILE)
(正確にはすでに展開されているので date > /tmp/sample.txt
) が実行されるのは、全ての sleep
(2, 4, 6行目) が行われた後なので、ファイルに書かれた日付が最も (5行目の echo $(shell date)
よりも) 新しくなる。
では、ファイル書き込みがあってその最新の内容を取りたいときはどうすればいいか。レシピが実行される前に展開されても、まだ cat
は実行されない状態にするには次のようにすれば良い。
FILE := /tmp/example.txt $(shell echo 'Old contents' > $(FILE)) CONTENTS = $$(cat $(FILE)) # instead of $(shell cat $(FILE)) .PHONY: all all: echo $(CONTENTS) echo 'New contents' > $(FILE) echo $(CONTENTS)
そう、シェルの展開に変えてしまうのだ。 (もうこうなると :=
を使っても同じになる)
echo $(cat /tmp/example.txt) Old contents echo 'New contents' > /tmp/example.txt echo $(cat /tmp/example.txt) New contents
多くの場合は $(shell ...)
が使えるのでこれを使っていたが、これが使えないケースがあることに気がついて少しワクワクした。
stackoverflow.com めっちゃ混乱してstackoverflowに投げたら解決した。
- 作者:Robert Mecklenburg
- 発売日: 2005/12/01
- メディア: 大型本