Closed19

Makefileのわからないところメモ

bayamasabayamasa

Makefileの構文は以下のようになる

target … : prerequisites …
        command
        …
        …
ターゲット : 依存関係ファイル
        コマンド

make targetとCLIで入力することにより以下のような順番で動作が走る

  1. targetが記載されている行の操作を開始する
  2. prerequisitesを確認して、そのファイルが存在するかを見る。
  3. もしファイルが存在しない場合、そのファイルが作成するように操作を実行する。
  4. ファイルが作成されたら、commandを実行する。
bayamasabayamasa

Case別 Makefile操作
Makefileの記載とそれに対する結果を見ていく
ファイル構成

.
├── Makefile
└── foo.c

以下のコマンドを実行して、fooという実行可能ファイルを作成する
targetやprere

gcc foo.c -o foo
bayamasabayamasa

CASE 1 :target = test

test:
    gcc foo.c -o foo
❯  make test
gcc foo.c -o foo
❯  make test
gcc foo.c -o foo
  1. fooの実行ファイルが作成される
  2. 同じコマンドが何回も実行できる。
    ※ 42的にはrelinkなのでNG
bayamasabayamasa

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.
  1. fooの実行可能ファイルが作成される
  2. fooが作成済みのときに操作が実行されない。

このことから、target名と作成される実行ファイル名を同じにすることで、relinkが起きないようにすることができることがわかる。

bayamasabayamasa

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にも記載されていないのでエラーとなり、処理を中止します。

bayamasabayamasa

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
  1. target fooが呼ばれる。
  2. 依存関係があるtarget barが呼ばれる。
  3. target barのechoが呼ばれる。
  4. fooに記載してある、gcc ~が呼ばれる。

このようにfoo → barのように依存先が呼ばれるような形になる。
ちなみに、Makefikeは標準出力にコマンド名→コマンドの出力結果の順番で読んでいくので、echo barと記載したときは 記載したコマンド echo barがechoされ、その後にコマンドの出力結果barが呼ばれる。記載コマンドを出力したくないときは@を追記する

bar: 
	@echo bar
❯  make bar
bar
bayamasabayamasa

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)が存在することで、依存関係が満たされているという風に認識されるからである。

bayamasabayamasa

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
bayamasabayamasa

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
bayamasabayamasa

応用編
よく以下の様な記述があるが、この場合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}
bayamasabayamasa

擬似ターゲット (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
bayamasabayamasa

自動変数
Makefile内でのみ利用可能な変数。
これにより、より簡易な記述が可能となる。

$@: target名
コマンド内に記述することで、コマンドにおけるターゲットの参照ができる。

ex)

hello:
	@echo $@
❯  make hello     
hello
bayamasabayamasa

$<: 最初の依存関係名

ex)

hello: hoge fuga
	@echo $<
hoge: 
	@echo hogehoge
fuga:
	@echo fugafuga
❯  make hello
hogehoge
fugafuga
hoge

依存関係に記載されている順番に呼び出され、最後に最初の依存関係の"hoge"のみ出力される。
このように依存関係が2つある場合に、最初の依存関係名だけを出力する。

bayamasabayamasa

$^: 全ての依存関係

hello: hoge fuga
	@echo $^
hoge: piyo
	@echo hogehoge
fuga:
	@echo fugafuga
piyo:
	@echo piyopiyo
❯  make hello
piyopiyo
hogehoge
fugafuga
hoge fuga

なお依存先の依存は出力されない

このスクラップは2022/05/04にクローズされました