Makeメモ: ファイルを作成するときは、ターゲットは実際のファイル名にしなければならない

Makefileに書かれたコマンドでファイルを作成するとき、ターゲットは実際のファイル名にしなければならないことに注意。

Makefileのターゲットの指定方法

Makefileでは、作成したいファイルと、そのファイルを作るためのソースファイル群との依存関係を指定する。

ターゲット: 依存関係にあるソースファイル群
  コマンド1
  コマンド2
  ...

このときmakeは、"ターゲット"に書かれたファイルのタイムスタンプと、"依存関係にあるソースファイル群"に書かれた各ファイルのタイムスタンプとを比較して、後者のいずれかが前者より新しければ、コマンドを実行する。

このことは、"ターゲット"には、コマンドで作成するファイル名を指定しなければならないことを意味する。もしそうせず、かつ"ターゲット"に書かれてある文字列が表すファイルがない場合、ターゲットは「phony target」(作成したいファイルを表していない、偽のターゲット)であると判断され、「無限に古いファイル」として扱われる。よってmakeは、ソースファイルのほうが新しいと判断し、必ずコマンドを実行してしまう。

失敗例

つまり、こういうことだ。

間違った書き方:

all: myfunc1 myfunc2

# 作成ファイルは"mf1"だが、ターゲットは"myfunc1"
myfunc1: myfucn1.c myheader.h
  gcc -o mf1 myfunc1.c 

myfunc2: myfunc2.c myheader.h
  gcc -o mf2 myfunc2.c 

上例のMakefileについて、makeを二回実行しても、二回目もコンパイルを行ってしまう。

二回目のmakeでは次のようなことが起きている。前提として、myfunc1という名のファイルは存在しないとする。

  1. 一番最初のターゲットはallなので、ターゲットallの行を見る。
  2. 最初のソースmyfunc1について、それがMakefile中でターゲットとして存在しているかどうか確認。
  3. ターゲットとして存在している。よって次はターゲットmyfunc1の行を見る。
  4. 最初のソースmyfunc1.cについて、それがMakefile中でターゲットとして存在しているかどうか確認。
  5. ターゲットとして存在しない。また、myheader.hも同様に存在しない。この場合、ソースとターゲットのタイムスタンプを比較して、コマンドを実行するかどうかを判断する。
  6. ターゲットmyfunc1と同名のファイルは存在しない。よって、ソースのほうが新しいと判断し、(必要ないにもかかわらず)コマンドgcc -o mf1 myfunc1.cを実行する。

成功例

正しい書き方:

all: mf1 mf2

# 作成ファイル、ターゲットともに"mf1"
mf1: myfucn1.c myheader.h
  gcc -o mf1 myfunc1.c 

mf2: myfunc2.c myheader.h
  gcc -o mf2 myfunc2.c 

余談

clean:
  rm -f *.o

などと書いて、make cleanで必ずコマンドが実行されるのも、ターゲットcleanと同名のファイルが存在しないからである。もしcleanという名のファイルが存在したら、make cleanしても何も起こらない*1

".PHONY: clean" という行を書いて、cleanがファイルではないことを明示すれば、ターゲットcleanの行以下のコマンドを必ず実行できる。ちなみに.PHONY行はどの位置に書いてもOK。

まとめ

  • ターゲットに書かれた文字列と同名のファイルが存在しないとき、ターゲットは「phony target」として扱われ、コマンドが必ず実行される。
  • cleanやallなどをphony targetとしてターゲットの部分に書くときは、必ず .PHONY: all clean という行も書こう。

*1:「cleanは更新済み」と言われる。何にも依存しないファイルであるcleanを更新しようとしているのだから、何もする必要がないわけだ。