Makefileのわからないところメモ
=
と:=
の違い
-
=
遅延評価 -
:=
即時評価
基本的に遅延評価になると、予期せぬというかわかりにくいエラーが発生してしまうので、:=を使ったほうがMakefile自体のdebugはしやすい。
参考
Makefileの構文は以下のようになる
target … : prerequisites …
command
…
…
ターゲット : 依存関係ファイル
コマンド
make target
とCLIで入力することにより以下のような順番で動作が走る
- targetが記載されている行の操作を開始する
- prerequisitesを確認して、そのファイルが存在するかを見る。
- もしファイルが存在しない場合、そのファイルが作成するように操作を実行する。
- ファイルが作成されたら、commandを実行する。
Case別 Makefile操作
Makefileの記載とそれに対する結果を見ていく
ファイル構成
.
├── Makefile
└── foo.c
以下のコマンドを実行して、fooという実行可能ファイルを作成する
targetやprere
gcc foo.c -o foo
CASE 1 :target = test
test:
gcc foo.c -o foo
❯ make test
gcc foo.c -o foo
❯ make test
gcc foo.c -o foo
- fooの実行ファイルが作成される
- 同じコマンドが何回も実行できる。
※ 42的にはrelinkなのでNG
CASE 2 : target = foo
foo:
gcc foo.c -o foo
❯ make foo
gcc foo.c -o foo
❯ make foo
make: `foo' is up to date.
- fooの実行可能ファイルが作成される
- fooが作成済みのときに操作が実行されない。
このことから、target名と作成される実行ファイル名を同じにすることで、relinkが起きないようにすることができることがわかる。
CASE 3 target = foo, prerequisites = bar
ファイル: barはディレクトリに存在しない状態
.
├── Makefile
└── foo.c
foo: bar
gcc foo.c -o foo
❯ make foo
make: *** No rule to make target `bar.c', needed by `foo'. Stop.
直訳すると、fooをmakeするために必要なtarget bar.cは存在しないので操作を中止します。
です。
barというファイルがディレクトリ上に存在しないので、Makefileにtargetが記載されていないかを探します。targetにも記載されていないのでエラーとなり、処理を中止します。
CASE 4 target = foo, bar prerequisites = bar
CASE3でtarget barが必要だと言われたので、barを追加してみる
foo: bar
gcc foo.c -o foo
bar:
echo bar
❯ make foo
echo test
test
gcc foo.c -o foo
❯ make bar
echo bar
bar
- target fooが呼ばれる。
- 依存関係があるtarget barが呼ばれる。
- target barのechoが呼ばれる。
- fooに記載してある、gcc ~が呼ばれる。
このようにfoo → barのように依存先が呼ばれるような形になる。
ちなみに、Makefikeは標準出力にコマンド名→コマンドの出力結果
の順番で読んでいくので、echo barと記載したときは 記載したコマンド echo bar
がechoされ、その後にコマンドの出力結果bar
が呼ばれる。記載コマンドを出力したくないときは@
を追記する
bar:
@echo bar
❯ make bar
bar
CASE 5 target = foo prerequisites = bar.c
ここから特殊なケース
prerequisitesに指定されたファイル名がディレクトリ上に存在する場合、動作が変わってくる
.
├── Makefile
├── bar.c
└── foo.c
foo: bar.c
gcc foo.c -o foo
❯ make foo
gcc foo.c -o foo
先程と違ってtargetにbar.cの記載がなくてもコマンドが実行できる。
これはディレクトリ上にprerequisitesに記載されているファイル(bar.c)が存在することで、依存関係が満たされているという風に認識されるからである。
CASE 6 target = foo prerequisites = bar.o
先程と同じディレクトリ構成でprerequisitesを.c → .o
に変更してみる
.
├── Makefile
├── bar.c
└── foo.c
foo: bar.o
gcc foo.c -o foo
❯ make foo
cc -c -o bar.o bar.c
gcc foo.c -o foo
この場合Makefileがbar.cがディレクトリに存在することにより、自動的にbar.oをコンパイルしてくれる
コンパイルにより、依存関係が解決したのでfooがコンパイルできる。
ちなみにbar.cのコンパイルはcc
によって行われているが、コンパイルオプションを変更することにより、コンパイルをgccにすることができる
CC = gcc
foo: bar.o
gcc foo.c -o foo
❯ make foo
gcc -c -o bar.o bar.c
gcc foo.c -o foo
CASE 7 target = foo prerequisites = bar
また同じディレクトリ構成でprerequisitesをbar
のみにする
foo: bar
gcc foo.c -o foo
❯ make foo
cc bar.c -o bar
gcc foo.c -o foo
ディレクトリにprerequisitesと同じ名前のファイル.cがあるときに、自動的に実行可能ファイルを作ってくれる。ちなみに.oファイルがあるときも自動的に作成してくれる。
❯ make foo
cc bar.o -o bar
gcc foo.c -o foo
応用編
よく以下の様な記述があるが、この場合makeを実行すると
all → ${NAME} → ${OBJS}
が実行され、OBJSは自動的に同ディレクトリにある .cファイルを探してコンパイルするようになる。(その自動指定コンパイルのオプションが.c.o
)
オブジェクトファイルが作成されたことで $(NAME) のcommand内の $(NAME)が使用可能になる。
$(NAME)が作成され、allが終了する。
自動変数と疑似ターゲットは後ほど記載する
SRC = main.c
OBJS = ${SRCS:.c=.o}
all: ${NAME}
$(NAME) : $(OBJS)
$(CC) $(CFLAGS) $(INC) $(OBJS) -o $(NAME)
.c.o:
${CC} ${CFLAGS} $(INC) -c $< -o ${<:.c=.o}
暗黙ルール
後でまとめる
擬似ターゲット (phony)
上にあるように元々targetとはファイルを対象にする項目である。
しかし、Makefileにおいて記述したcommandだけ実行したい みたいな場合が存在する。
例えばよく使用されるcleanコマンドは、ファイルを削除するだけの操作なので、command操作をした後に作成する、targetファイルなどは存在しない。
このようなときはtargetに対して、ファイル名ではない文字を使用する。
ex)
clean:
${RM} ${OBJS}
fclean: clean
${RM} ${NAME}
re: fclean all
こうすることで、make clean, fclean, re
などを行える。
しかしtargetは優先順位としてファイル名を参照する。
なので擬似ターゲットに指定したファイルがディレクトリに存在した場合、makeがエラーになる。
ex)
.
├── Makefile
└── clean
❯ make clean
make: `clean' is up to date.
擬似ターゲットにおけるmakeは必ず実行できてほしいので、ファイルによってcommandが実行できないという自体は避けたい。ここでphonyを使う。
Makefile内に.PHONY:
を使うことで、たとえ同ディレクトリ内部にtargetと同じファイルが存在したとしても、それを無視してコマンドを実行してくれる。
clean:
gcc foo.c -o foo
.PHONY: clean
❯ make clean
gcc foo.c -o foo
自動変数
Makefile内でのみ利用可能な変数。
これにより、より簡易な記述が可能となる。
$@
: target名
コマンド内に記述することで、コマンドにおけるターゲットの参照ができる。
ex)
hello:
@echo $@
❯ make hello
hello
$<
: 最初の依存関係名
ex)
hello: hoge fuga
@echo $<
hoge:
@echo hogehoge
fuga:
@echo fugafuga
❯ make hello
hogehoge
fugafuga
hoge
依存関係に記載されている順番に呼び出され、最後に最初の依存関係の"hoge"のみ出力される。
このように依存関係が2つある場合に、最初の依存関係名だけを出力する。
$^
: 全ての依存関係
hello: hoge fuga
@echo $^
hoge: piyo
@echo hogehoge
fuga:
@echo fugafuga
piyo:
@echo piyopiyo
❯ make hello
piyopiyo
hogehoge
fugafuga
hoge fuga
なお依存先の依存は出力されない
他の自動変数の説明などがわかりやすく書いてあるサイト