ヘッダファイル、ソースファイル、オブジェクトファイルを別々のフォルダに保存するMakefileのサンプル

# はじめに
2022/12/14 00:29 大幅に加筆修正をしました。

この記事は[長野高専 Advent Calender 2022](https://qiita.com/advent-calendar/2022/nnct "長野高専 Advent Calender 2022") 13日目の記事です。
長野高専電子情報工学科4年のプログラミング演習でゲームを製作する人が対象です。
なので、cygwinやOpenGLを使用することだったり、Makefileに関する授業を受けていることは前提で進めていきます。
明日の記事もそんな感じです。

今回は、ソースファイル、ヘッダファイル、オブジェクトファイルがそれぞれ別のフォルダに格納されるMakefileを作ります。

# 復習 Makefileとは
Makefileは、依存関係の管理やコンパイルを行うルールを記述しておくファイルです。
`make`コマンドを実行することによってMakefileの中身を読み込んで処理が行われます。
つまり、長ったらしいコンパイルのコマンドをMakefileに書いておいて、`make`コマンドを実行するだけで処理を行うようにするための便利アイテムです。

# 本題
それでは本題に入っていきます。今回のディレクトリ構成はこんな感じです。ponyaponyaというゲームを作ることを想定します。
```:ディレクトリ構成
ponyaponya/
    ├ src/
    │  ├ main.c
    │  ├ hogehoge.c
    │  └ hugahuga.c
    ├ inc/
    │  ├ hogehoge.h
    │  └ hugahuga.h
    ├ obj/
    │  ├ main.o
    │  ├ hogehoge.o
    │  └ hugahuga.o
    ├ Makefile
    └ ponyaponya.exe
```
srcフォルダの中には.cファイル、incフォルダの中には.hファイル、objフォルダの中には.oファイルが入ります。

## とりあえずソース
ソースはこれ
```Makefile:Makefile
#  最終目的のファイル
TARGET = ponyaponya.exe

#  ソースファイル(*.c)の一覧
SRCDIR = src/
SRCS = ${wildcard ${SRCDIR}*.c}

#  オブジェクトファイル(*.o)の一覧
OBJDIR = obj/
OBJS = ${patsubst %.c,%.o,${SRCS}}
OBJECTS = ${addprefix ${OBJDIR}, ${notdir ${OBJS}}}

#  ヘッダファイルの一覧
HEADDIR = inc/
HEADERS = ${wildcard ${HEADDIR}*.h}

#  コンパイラ・リンカの指定
CC = i686-pc-cygwin-gcc
CCFLAGS = -Wall -I/usr/include/opengl -I ./inc
LD = $(CC)
LDFLAGS = 
LIBS = -lm -lglpng -lglut32 -lglu32 -lopengl32

#  *.cから*.oを作る方法
.c.o :
		$(CC) $(CCFLAGS) -o ${patsubst ${SRCDIR}%,${OBJDIR}%,$@} -c $< 

#  OBJECTSからTARGETSを作る方法
$(TARGET) : $(OBJS)
		$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)

#  make cleanとしたときに実行されるコマンド
clean :
		rm -f $(OBJECTS) $(TARGET) core *~

exe : $(TARGET)
	./$(TARGET)

all : clean $(TARGET) exe
```
## 実行結果
実行すると、このような結果になります。
```console
$ make
i686-pc-cygwin-gcc -Wall -I/usr/include/opengl -I ./inc -o obj/hogehoge.o -c src/hogehoge.c
i686-pc-cygwin-gcc -Wall -I/usr/include/opengl -I ./inc -o obj/hugahuga.o -c src/hugahuga.c
i686-pc-cygwin-gcc -Wall -I/usr/include/opengl -I ./inc -o obj/main.o -c src/main.c
i686-pc-cygwin-gcc obj/hogehoge.o obj/hugahuga.o obj/main.o -o ponyaponya.exe 
                                      -lm -lglpng -lglut32 -lglu32 -lopengl32
```

## 説明
先ほどのソースを1行ずつ見ていきます。

```Makefile
TARGET = ponyaponya.exe
```

ここでは、最終的に出力したいファイルを表す`TARGET`という変数を宣言しています。

```Makefile
SRCDIR = src/
SRCS = ${wildcard ${SRCDIR}*.c}
```
src/内の.cファイルを`wildcard`関数によってすべて抜き出し、その結果を`SRCS`という変数に格納しています。

```Makefile
OBJDIR = obj/
OBJS = ${patsubst %.c,%.o,${SRCS}}
OBJECTS = ${addprefix ${OBJDIR}, ${notdir ${OBJS}}}
```
`SRCS`変数に格納された.cファイル群の.cという文字列を`patsubst`関数で.oに置き換え、`OBJS`という変数に格納しています。
`OBJECTS`変数には、`OBJS`変数のsrc/を`notdir`関数で消し、`addprefix`関数でobj/を付け加えたものを格納しています。

```Makefile
CC = i686-pc-cygwin-gcc
CCFLAGS = -Wall -I/usr/include/opengl -I ./inc
LD = $(CC)
LDFLAGS = 
LIBS = -lm -lglpng -lglut32 -lglu32 -lopengl32
```

ここでは、各コンパイラとリンクの設定を行っています。
`CCFLAGS`変数の最後に./incを付けてヘッダファイルを読み込むようにしています。

```Makefile
.c.o :
		$(CC) $(CCFLAGS) -o ${patsubst ${SRCDIR}%,${OBJDIR}%,$@} -c $<
```
.cファイルから.oファイルを作る時のルールを設定しています。
`${patsubst ${SRCDIR}%,${OBJDIR}%,$@}`の部分では、.oを出力するファイル名を指定しています。
`$@`にはターゲット名が入るMakefileの特殊変数です。今回の場合は`src/hogehoge.o`、`src/hugahuga.o`の二つになります。ただし、このままではobjectファイルがobj/ではなくsrc/に入ってしまうので、`patsubst`関数でsrc/をobj/に置き換えています。

```Makefile
$(TARGET) : $(OBJS)
		$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)
```
今まで作ったオブジェクトファイルから、`TARGET`を作り出します。

## 問題点
このMakefileでは、タイムスタンプを利用した必要なファイルだけ再コンパイルする機能が使えていません。なので、makeコマンドを2回連続で実行しても、`'ponyaponya' is up to date.`と表示されずにオブジェクトファイルすべてを作り直してしまいます。
原因として、
```Makefile
#  OBJECTSからTARGETSを作る方法
$(TARGET) : $(OBJS)
		$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)
```
ここの`$(TARGET)`の依存先である`$(OBJS)`が`src/hogehoge.o`とかになっていることが考えられます。
    Makefileは`TARGET`の依存先が更新されたり存在しなかったらそっちの処理を行うので、
`src/hogehoge.o`が存在しない！→作ろう！→`patsubst`により出力先がobj/に変更
の流れができてしまいこのように更新してないのに無限makeが行われるのだと思います。これを何とかしましょう。
## 改変しよう
では、タイムスタンプを参照した「改変したファイルの部分だけオブジェクトファイルを作り直す」という機能を使うようにしましょう。
`$(OBJS)`の中身が`src/hogehoge.o`とかになっていることが原因なら、`$(OBJECTS)`にして`src/hogehoge.o`を`obj/hogehoge.o`にすればよさそうですね。
具体的にはこんな感じ
```Makefile
$(TARGET) : $(OBJECTS)
		$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)
```
純粋に`$(OBJS)`を`$(OBJECTS)`に変更しただけです。この状態で実行してみましょう。
```console
$ make
i686-pc-cygwin-gcc obj/hogehoge.o obj/hugahuga.o obj/main.o -o ponyaponya.exe 
                                      -lm -lglpng -lglut32 -lglu32 -lopengl32
i686-pc-cygwin-gcc error: obj/hogehoge.o: No such file or directory
i686-pc-cygwin-gcc error: obj/hugahuga.o: No such file or directory
i686-pc-cygwin-gcc error: obj/main.o: No such file or directory
```
むむ、エラーが出てしまいました。エラー内容としては、オブジェクトファイルが見つからないよ～＞＜って言われてます。
どうやら.cファイルから.oが作られていませんね...
ということで、`.c.o`の部分を改変したらよさそうです。
この部分ですね。
```Makefile
.c.o :
		$(CC) $(CCFLAGS) -o ${patsubst ${SRCDIR}%,${OBJDIR}%,$@} -c $<
```
`.c.o`の部分を変更しました。
```Makefile
obj/%.o: src/%.c
		$(CC) $(CCFLAGS) -c $< -o ${patsubst src/%.o,obj/%.o,$@} 
```
これで、例えば`hugahuga.c`に変更を加えた時に`hugahuga.o`のみ作り直されて`ponyaponya.exe`が作られるようになりました。しかし、これにはまだ問題点があります。それは、**ヘッダファイルが変更された時にはオブジェクトファイルは作り直されない**ということです。
## 更に改変しよう
ということで、次の一文を加えます。
```Makefile
#  *.oはHEADERSとMakefileに依存(これらが書き変わった時にも*.oを再構築)
$(OBJECTS) : $(HEADERS) Makefile
```
これで、ヘッダファイルに改変があった時にオブジェクトファイルを作り直すようになりました。ただし、すべてのオブジェクトファイルを作り直してしまいます。これは書き換えたヘッダファイルが別のヘッダファイルにincludeされてたりしたらそのヘッダファイルも作り直さないといけないので、仕方ない気がします。

そしてこれが全ての問題点を解決して、あとは色々手を加えたコードになります。
```Makefile
# プログラムの名前
TARGET = ponyaponya.exe

#  ソースファイル(*.c)の一覧
SOURCES = $(wildcard src/*.c)

#  ヘッダファイル(*.h)の一覧
HEADERS = $(wildcard inc/*.h)

#  オブジェクトファイル(*.o)の一覧
OBJECTS = ${patsubst src/%.c,obj/%.o,${SOURCES}}

#  コンパイラ・リンカの指定
CC = i686-pc-cygwin-gcc
CCFLAGS = -Wall -I/usr/include/opengl -I ./inc
LD = $(CC)
LDFLAGS = 
LIBS = -lm -lglpng -lglut32 -lglu32 -lopengl32

#  オブジェクトファイルの作成
obj/%.o : src/%.c
		$(CC) $(CCFLAGS) -c $< -o ${patsubst src/%.o,obj/%.o,$@} 

#  オブジェクトファイルからターゲットを作成
$(TARGET) : $(OBJECTS)
		$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)

#  *.oはHEADERSとMakefileに依存(これらが書き変わった時にも*.oを再構築)
$(OBJECTS) : $(HEADERS) Makefile


#  オブジェクトファイルの削除
.PHONY : clean
	rm -f $(OBJECTS) $(TARGET) core *~
```
# 終わりに
いかがでしたか？
最近では.dファイルの作成が推奨されているらしいです。なんかここに依存関係とか書くらしい。よくわからないけど。
ソースファイルを複数のディレクトリに分けたい！とかの場合は
`OBJECTS = ${patsubst src/%.c,obj/%.o,${SOURCES}}`を
`OBJECTS = ${addprefix obj/, ${notdir ${SOURCES}}}`とかにして、フォルダの関係を全部直したら出来そう。試してないけど


おしまい


# 参考文献
[トリビアなmakefile入門](http://www.jsk.t.u-tokyo.ac.jp/~k-okada/makefile/)