はじめに
2022/12/14 00:29 大幅に加筆修正をしました。
この記事は長野高専 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ファイルが入ります。
とりあえずソース
ソースはこれ
# 最終目的のファイル
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
実行結果
実行すると、このような結果になります。
$ 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行ずつ見ていきます。
TARGET = ponyaponya.exe
ここでは、最終的に出力したいファイルを表すTARGET
という変数を宣言しています。
SRCDIR = src/
SRCS = ${wildcard ${SRCDIR}*.c}
src/内の.cファイルをwildcard
関数によってすべて抜き出し、その結果をSRCS
という変数に格納しています。
OBJDIR = obj/
OBJS = ${patsubst %.c,%.o,${SRCS}}
OBJECTS = ${addprefix ${OBJDIR}, ${notdir ${OBJS}}}
SRCS
変数に格納された.cファイル群の.cという文字列をpatsubst
関数で.oに置き換え、OBJS
という変数に格納しています。
OBJECTS
変数には、OBJS
変数のsrc/をnotdir
関数で消し、addprefix
関数でobj/を付け加えたものを格納しています。
CC = i686-pc-cygwin-gcc
CCFLAGS = -Wall -I/usr/include/opengl -I ./inc
LD = $(CC)
LDFLAGS =
LIBS = -lm -lglpng -lglut32 -lglu32 -lopengl32
ここでは、各コンパイラとリンクの設定を行っています。
CCFLAGS
変数の最後に./incを付けてヘッダファイルを読み込むようにしています。
.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/に置き換えています。
$(TARGET) : $(OBJS)
$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)
今まで作ったオブジェクトファイルから、TARGET
を作り出します。
問題点
このMakefileでは、タイムスタンプを利用した必要なファイルだけ再コンパイルする機能が使えていません。なので、makeコマンドを2回連続で実行しても、'ponyaponya' is up to date.
と表示されずにオブジェクトファイルすべてを作り直してしまいます。
原因として、
# 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
にすればよさそうですね。
具体的にはこんな感じ
$(TARGET) : $(OBJECTS)
$(LD) $(OBJECTS) $(LDFLAGS) -o $(TARGET) $(LIBS)
純粋に$(OBJS)
を$(OBJECTS)
に変更しただけです。この状態で実行してみましょう。
$ 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
の部分を改変したらよさそうです。
この部分ですね。
.c.o :
$(CC) $(CCFLAGS) -o ${patsubst ${SRCDIR}%,${OBJDIR}%,$@} -c $<
.c.o
の部分を変更しました。
obj/%.o: src/%.c
$(CC) $(CCFLAGS) -c $< -o ${patsubst src/%.o,obj/%.o,$@}
これで、例えばhugahuga.c
に変更を加えた時にhugahuga.o
のみ作り直されてponyaponya.exe
が作られるようになりました。しかし、これにはまだ問題点があります。それは、ヘッダファイルが変更された時にはオブジェクトファイルは作り直されないということです。
更に改変しよう
ということで、次の一文を加えます。
# *.oはHEADERSとMakefileに依存(これらが書き変わった時にも*.oを再構築)
$(OBJECTS) : $(HEADERS) Makefile
これで、ヘッダファイルに改変があった時にオブジェクトファイルを作り直すようになりました。ただし、すべてのオブジェクトファイルを作り直してしまいます。これは書き換えたヘッダファイルが別のヘッダファイルにincludeされてたりしたらそのヘッダファイルも作り直さないといけないので、仕方ない気がします。
そしてこれが全ての問題点を解決して、あとは色々手を加えたコードになります。
# プログラムの名前
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}}}
とかにして、フォルダの関係を全部直したら出来そう。試してないけど
おしまい