LoginSignup
6
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-13

はじめに

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ファイルが入ります。

とりあえずソース

ソースはこれ

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

実行結果

実行すると、このような結果になります。

$ 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.osrc/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.oobj/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}}}とかにして、フォルダの関係を全部直したら出来そう。試してないけど

おしまい

参考文献

トリビアなmakefile入門

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3