読者です 読者をやめる 読者になる 読者になる

make Makefileで階層(サブディレクトリ)を走破しながら依存を解決してコンパイルする

C++でクラス毎にファイルを分け、ファイル群をディレクトリ構造で管理するのは一般的な方法かと思います。ただ階層化したディレクトリの依存関係を解決しながら1つの実行ファイルを生成するのにはコツがいるようです。

一般的には、サブディレクトリ毎にMakefileを設置して多段コンパイルするようですが、1つの目的で複数のファイルがあるとメンテナンス性も落ちるしトラブルを孕みやすいので、1つのMakefileで解決する方法がないか模索しました。

というわけで以下のMakefileで目的が達成されました。

# コンパイラ 僕はOSXでC++なのでclang++
COMPILER  = clang++
# お好きなフラグを
CXXFLAGS    = -g -O0 -MMD -Wall -Wextra -Winit-self -std=c++11 -stdlib=libc++
# ライブラリ関係の指定
ifeq "$(shell getconf LONG_BIT)" "64"
  LDFLAGS = -L/usr/local/lib
else
  LDFLAGS = -L/usr/local/lib
endif
LIBS      =
# インクルードパスの指定。これをちゃんとしておかないとDEPENDS(依存)ファイルがうまく作れない
INCLUDE   = -I../include -I/usr/local/include
# 生成される実行ファイル
TARGETS   = a.out
# 生成されるバイナリファイルの出力ディレクトリ
TARGETDIR = ../bin
# ソースコードの位置
SRCROOT   = .
# 中間バイナリファイルの出力ディレクトリ
OBJROOT   = ../obj
# ソースディレクトリの中を(shellの)findコマンドで走破してサブディレクトリまでリスト化する
SRCDIRS  := $(shell find $(SRCROOT) -type d)
# ソースディレクトリを元にforeach命令で全cppファイルをリスト化する
SOURCES   = $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.cpp))
# 上記のcppファイルのリストを元にオブジェクトファイル名を決定
OBJECTS   = $(addprefix $(OBJROOT)/, $(SOURCES:.cpp=.o)) 
# ソースディレクトリと同じ構造で中間バイナリファイルの出力ディレクトリをリスト化
OBJDIRS   = $(addprefix $(OBJROOT)/, $(SRCDIRS)) 
# 依存ファイル.dを.oファイルから作る
DEPENDS   = $(OBJECTS:.o=.d)

# 依存ファイルを元に実行ファイルを作る
$(TARGETS): $(OBJECTS) $(LIBS)
	$(COMPILER) -o $(TARGETDIR)/$@ $^ $(LDFLAGS)

# 中間バイナリのディレクトリを掘りながら.cppを中間ファイル.oに
$(OBJROOT)/%.o: $(SRCROOT)/%.cpp
	@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
	$(COMPILER) $(CXXFLAGS) $(INCLUDE) -o $@ -c $<

# 依存関係ファイル
-include $(DEPENDS)

ディレクトリ構成は以下を想定しています。

example
|-- bin
|   `-- a.out   <- 出力される実行ファイル
|-- include
|   |--sub
|   |   `-- sub_example.h   <- 階層化されたクラスのヘッダファイル
|   `-- example.h
|-- obj 
|   |--sub    <- src以下のディレクトリ構成を反映させたディレクトリ(自動生成)
|   |   |--  sub_example.d    <- 階層化されたクラスの依存関係ファイル(自動生成)
|   |   `-- sub_example.o    <- 階層化されたクラスの中間バイナリファイル(自動生成)
|   |-- example.d
|   `-- example.o
`-- src
    |--sub
    |   `-- sub_example.cpp   <- 階層化されたソース・ファイル
    |--  Makefile     <- example/srcが起点
    `-- example.cpp

ベースは以下を参考にさせてもらいました。CXXFLAGSの解説は以下を参考にしてください。
urin.github.io

また問題が出たら修正版を書こうと思います。

最近はCMakeやSConsというモダンなタスクランナーが存在するのでMakeに頼る必要はもはやないかと思いますが、Makeでどうしても、という人はご参考にどうぞ。