💬

【再帰的な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.alib2.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.alib2.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.alib2.aが更新されていなければ、bin/appのビルドはトリガーされない。

GitHubで編集を提案

Discussion