複数プロジェクトが併存できるC++用の汎用的なMakefileを作ってみた

#概要
`cmake`を使っても良いのだけど、ちょっとしたC++のコードを書くときに`CMakeLists.txt`を一から書くのもなんとなく億劫だったので、拡張が容易かつジェネリックな`Makefile`を書いてみました。また`Makefile`を`Makefile`と`Makefile.in`の２つに分離したことにより、ソースコードを共有する複数のプロジェクトのビルドも容易にできます。


#使い方
1. プロジェクトが存在するディレクトリに、後ほど説明する`Makefile`と`Makefile.in`を入れてください。
2. ソースコードを`./src`以下に、ライブラリを`./include`以下に入れてください。
3. `Makefile`を編集して、各プロジェクトに対してコンパイル&リンクするソースコードを指定してください。
4. 以上の作業で完了です。あとは自由に`Makefile`のコマンドが使えます。

以下に使用できるコマンドをまとめました(`[TARGET]`は`Makefile`内で指定するターゲット名)。
なお実行ファイルは`./bin`以下に、オブジェクトファイルは`./obj`以下に自動的に生成されます。

|コマンド|説明|
|---|---|
|`make [TARGET]`|`make [TARGET].release`と同じ|
|`make [TARGET].release`|最適化オプションを適用し`[TARGET]`をビルドする|
|`make [TARGET].develop`|デバッグ用に`[TARGET]`をビルドする|
|`make [TARGET].all`|`[TARGET]`を強制的にビルドする|
|`make [TARGET].run`|生成された`[TARGET]`の実行ファイルを実行する|
|`make [TARGET].debug`|生成された`[TARGET]`の実行ファイルを`gdb`でデバッグする|
|`make [TARGET].clear`|生成された`[TARGET]`の実行ファイルを消去する|
|`make [TARGET].clean`|`[TARGET]`の実行ファイルとオブジェクトファイルを消去する|
|`make all`|すべてのプロジェクトをコンパイルする。|
|`make clear`|すべてのプロジェクトの実行ファイルを消去する。|
|`make clean`|すべてのプロジェクトの実行ファイルとオブジェクトファイルを消去する。|

#Sample & Makefileの解説
## Sample
実際にやってみましょう(サンプルコードのダウンロードは[こちら](https://github.com/feltmax/makefile_template)から)。
簡単のため、以下のような```./src```以下にソースコードが、```./include```以下にライブラリがあるようなディレクトリ構造のプロジェクトを考えてみます。ここで```prog_A```、```prog_B```の２つのプロジェクトのソースコードがあり、それぞれ```utils.hpp```に依存するような状況だとします。

```
.
├── Makefile
├── Makefile.in
├── include
│   └── utils
│       └── utils.hpp
└── src
    ├── prog_A
    │   ├── hoge.cpp
    │   ├── hoge.hpp
    │   └── main.cpp
    ├── prog_B
    │   ├── fuga.cpp
    │   ├── fuga.hpp
    │   └── main.cpp
    └── utils
        └── utils.cpp
```

## Makefileの設定
まず`Makefile`は以下のように設定します。

```make:Makefile
MAKEFLAGS += --no-print-directory --no-builtin-rules

.DEFAULT_GOAL := all

TARGET := prog_A prog_B

prog_A : ARGS += "BIN_NAME=prog_A"
prog_A : ARGS += "SRC_DIRS=./src/prog_A ./src/utils"

prog_B : ARGS += "BIN_NAME=prog_B"
prog_B : ARGS += "SRC_DIRS=./src/prog_B ./src/utils"

$(addsuffix .release,$(TARGET)) : OPTION += release
%.release : %
	@:

$(addsuffix .develop,$(TARGET)) : OPTION += develop
%.develop : %
	@:

$(addsuffix .all,$(TARGET)) : OPTION += all
%.all : %
	@:

$(addsuffix .run,$(TARGET)) : OPTION += run
%.run : %
	@:

$(addsuffix .debug,$(TARGET)) : OPTION += debug
%.debug : %
	@:

$(addsuffix .clear,$(TARGET)) : OPTION += clear
%.clear : %
	@:

$(addsuffix .clean,$(TARGET)) : OPTION += clean
%.clean : %
	@:

all : $(TARGET)
clear : $(addsuffix .clear,$(TARGET))
clean : $(addsuffix .clean,$(TARGET))

$(TARGET) :
	@echo "----- make $(OPTION) $@ -----"
	@make -f ./Makefile.in $(ARGS) $(OPTION)
```

ここで各プロジェクトに最低必要なのは以下のコードとなります。
各プロジェクトに対して`Makefile`内に以下のコードを記述してください。

```make:
[TARGET] : ARGS += "BIN_NAME=[実行ファイルの名前]"
[TARGET] : ARGS += "SRC_DIRS=[依存するソースコードが存在する相対ディレクトリ]"
```

また変数`TARGET`に追加したターゲットを加えれば完成です。

## Makefile.inの解説
次にコンパイラに詳細なオプションを与える`Makefile.in`を解説します。

```make:Makefile.in
MAKEFLAGS += --no-builtin-rules

CXX := g++
CXXFLAGS := -std=c++17 -Wall -m64 -MMD -MP
CXXFLAGS_DEV := -O0 -g
CXXFLAGS_REL := -O3
DEBUG := gdb
LIBS := -lpthread -lglut -lm
CPPFLAGS := $(addprefix -I, ./include $(wildcard ./include/*))
CPPFLAGS += `pkg-config --cflags opencv`
ifeq "$(shell getconf LONG_BIT)" "64"
  LDFLAGS := `pkg-config --libs opencv`
else
  LDFLAGS :=
endif
SUFFIX := cpp

BIN_NAME :=
BIN_DIR := ./bin
BINARY := $(BIN_DIR)/$(BIN_NAME)

SRC_DIR := ./src
SOURCES := $(SRC_FILES)
SOURCES += $(foreach dir,$(SRC_DIRS),$(shell find $(dir) -regex ".*\.$(SUFFIX)"))

OBJ_DIR := ./obj
OBJECTS := $(patsubst $(SRC_DIR)/%,$(OBJ_DIR)/%,$(SOURCES:.$(SUFFIX)=.o))

DEPENDS := $(OBJECTS:.o=.d)

.DEFAULT_GOAL := release

release : CXX_FLAGS += $(CXXFLAGS_REL)
release : $(BINARY)

develop : CXX_FLAGS += $(CXXFLAGS_DEV)
develop : $(BINARY)

$(BINARY) : $(OBJECTS) $(LIBS)
	mkdir -p $(@D)
	$(CXX) -o $@ $^ $(LDFLAGS)

$(OBJ_DIR)/%.o : $(SRC_DIR)/%.$(SUFFIX)
	mkdir -p $(@D)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<

all : clean release

debug :
	$(DEBUG) $(BINARY)

run:
	$(BINARY)

clear :
	-rm -rf $(BINARY)

clean :
	-rm -rf $(OBJECTS) $(BINARY)

.PHONY : all debug run clean clear

-include $(DEPENDS)
```

代表的なオプションについて説明します。

|option|説明|
|---|---|
|`CXX`|C++のコンパイラの種類|
|`CXXFLAGS`|オプション|
|`CXXFLAGS_DEV`|`make develop`時に与えるオプション|
|`CXXFLAGS_REL`|`make release`時に与えるオプション|
|`DEBUG`|デバッガのプログラム|
|`LIBS`|リンクするライブラリ|
|`CPPFLAGS`|プリプロセス時に使うオプション|
|`SUFFIX`|ソースコードの拡張子|

お好みでこれらのオプションを変えることができます。また、あるプロジェクトのみに変更を適応したい場合は、`Makefile`内の変数`ARGS`に`ARGS += "[OPTION]=[VALUE]"`とすれば設定できます。

## 補足
+ `make`のオプションに`--no-builtin-rules`を加えることで暗黙のルールを使用しないようにしています。これにより探索範囲が狭まり、さらに高速にコンパイルができるようになります。
+ 今回の例では拡張子を`cpp`と`hpp`に指定しましたが、`Makefile.in`内の変数`SUFFIX`を変更することで、別の拡張子を用いることができます。
+ 今回の例ではソースコードの場所をディレクトリで指定しましたが、`ARGS += "SRC_FILES=[PATH/TO/FILE]"`でファイルを直接指定することもできます。
+ `Makefile`に追記することでツールを拡張することができます。

# Download
https://github.com/Tamflex/makefile_template

#参考文献
以下の文献を参考にさせていただきましたm(_ _)m。
http://boysenberrypi.hatenadiary.jp/entry/2014/03/15/113703
http://nantonaku-shiawase.hatenablog.com/entry/20110905/1321356759
http://d.hatena.ne.jp/tanakaBox/20070327/1174958337