💬
【再帰的なMakefile】.PHONYとFORCEの違い
再帰的なMakefileを次のように書いているかもしれない。
.PHONY: build
build: bin/app
bin/app: lib/lib1.a lib/lib2.a
# ビルドする
.PHONY: lib/lib1.a
lib/lib1.a:
$(MAKE) -C src/lib1 build
.PHONY: lib/lib2.a
lib/lib2.a:
$(MAKE) -C src/lib2 build
しかし、依存関係に.PHONYとして指定されたターゲットがある場合、そのターゲットのタイムスタンプにかかわらず、常に実行される。つまり、呼び出し先のMakefileでlib1.aやlib2.aが更新されていなくても、毎回bin/appのビルドがトリガーされてしまう。
これを避けるために、.PHONYの代わりにFORCEターゲットを使用する方法がある。
.PHONY: build
build: bin/app
bin/app: lib/lib1.a lib/lib2.a
# ビルドする
lib/lib1.a: FORCE
$(MAKE) -C src/lib1 build
lib/lib2.a: FORCE
$(MAKE) -C src/lib2 build
# このように書いてもよい
# FORCE: ;
.PHONY: FORCE
FORCEが依存関係にあるターゲットは.PHONYとして指定された時と同じように常に実行されるが、.PHONYとは異なりタイムスタンプが考慮される。つまり、サブのMakefileが実行されていても、lib1.aやlib2.aが更新されていなければ、bin/appのビルドはトリガーされない。
bin/appの依存関係が.PHONYとして指定されているのが問題であるので、少し冗長だが.PHONYを使って書くこともできる。
.PHONY: build
build: bin/app
bin/app: lib/lib1.a lib/lib2.a
# ビルドする
lib/lib1.a: build-lib1
lib/lib2.a: build-lib2
.PHONY: build-lib1
build-lib1:
$(MAKE) -C src/lib1 build
.PHONY: build-lib2
build-lib2:
$(MAKE) -C src/lib2 build
このように書いても、lib1.aやlib2.aが更新されていなければ、bin/appのビルドはトリガーされない。
Discussion