ついにッ！最強のMakefileが完成したぞッッッ！！！

# はじめに

こんにちは、[42tokyo Advent Calendar 2021](https://qiita.com/advent-calendar/2021/42tokyo)の16日目を担当する、在校生のharinez2です。
エンジニア養成機関 42 Tokyoに所属しており、仕事をしながら課題を進めています。



# 便利なmake

CやC++のプログラムを書いていると、必ず出てくるのがmake、およびMakefileです。

makeは、CやC++などのコンパイルコマンドをMakefileという名前のファイルに記述しておけば、makeコマンド実行でそれらを簡単に実行できる便利コマンドです。

コンパイルコマンドと言いましたが、実際はコマンドの羅列ですので、動かしたいコマンドを何でも記述できます。



ただ、今まで何となくややこしい、どのように動くのかわからないと感じていました。その原因として、暗黙のルールや暗黙の変数がたくさんあるからだと思います。

今回はそのあたりを解きほぐし、C/C++をコンパイルするうえで便利なMakefileの書き方について説明していきます。



さっさと見たい人は[こちら](#%E3%81%93%E3%82%8C%E3%81%8C%E6%9C%80%E5%BC%B7%E3%81%AEmakefile%E3%81%A0%E3%83%83)



## 動作確認環境

```shell
>make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0
```



# 最低限覚えておきたい文法

## ルール

Makefileでは、ファイル名またはルール名と、そのルールを呼び出したときに実行するコマンドを羅列して記述します。

```makefile
<target>: <pre-requisite>
	<command>
```

これは、次のような動作をします。

- targetが呼ばれたとき、まず前提条件pre-requisiteのターゲットを実行する
- その後、targetファイルがない、または前提条件のファイルよりタイムスタンプが古い場合にcommandを実行する
- targetファイルがあり、かつ前提条件のファイルよりタイムスタンプが新しい場合は何もしない



例えば、Cのプログラムをコンパイルするルールを書く場合は以下のようになります。

```makefile
myprogram: myprogram_main.c
	gcc -o myprogram myprogram_main.c
```



また、上記のtargetの部分は、ルール名でも可です。

```makefile
.PHONY: <target>
<target>: <pre-requisite>
	<command-line>
```

ルール名を記述しても、makeはそれをファイル名として認識します。

ファイル名ではなくルール名であることを明示的に認識させるため、PHONYルールを記述しておくとよいです。

PHONYルールは`.PHONY: all clean fclean re`のように複数並べて書くこともできますが、増えてくると書き漏れるため、ルール名とPHONYルールを1対1で毎回書くのがおススメです。



## 変数

Makefileでは変数を定義できます。

```makefile
VARNAME = value
```

例を示します。

```makefile
NAME   = myprogram
SRC    = myprogram_main.c
CC     = gcc
CFLAGS = -Wall -Wextra -Werror
```

変数の使用は、$(NAME)のようにします。（${NAME}のように中括弧で括ってもよいです。）



先ほどのCのコンパイルルールは以下のように書けますね。

```makefile
NAME   = myprogram
SRC    = myprogram_main.c
CC     = gcc
CFLAGS = -Wall -Wextra -Werror

$(NAME): $(SRC)
	$(CC) -o $(NAME) $(SRC)
```



## 暗黙の変数

明示的に記述しなくても、すでに定義されている暗黙の変数があります。

[10.3 Variables Used by Implicit Rules](https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html)

[Makefile の書き方 (C 言語)　マクロ](https://ie.u-ryukyu.ac.jp/~e085739/c.makefile.tuts.html#id6)



実際にどのような値が設定されているかを確認してみましょう。

暗黙の変数の一覧を表示するには、-pオプションをつけて実行します。

```shell
make -p
```

たくさん表示されますので、grepするとよいです。

```shell
>make -p | grep CC
make: *** No targets specified and no makefile found.  Stop.
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)
CC = cc
CPP = $(CC) -E
YACC = yacc
~~~省略~~~
```



暗黙の変数をうまく使うことで、暗黙のルールなどをうまく活用しながら簡便に記述することができます。

例えば、変数CCはすでに定義されており、ccコマンドが代入されています。これを`CC = gcc`のように書き換えることで、コンパイルオプションなどはそのままで使用するコンパイラのみを変更することができます。



その他、自動変数と呼ばれる、便利に使える変数もあるためうまく使いましょう。

[10.5.3 Automatic Variables](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html)

[Makfile基本的書き方まとめ](https://kzky.hatenablog.com/entry/2014/12/21/Makfile基本的書き方まとめ)



## 暗黙のルール

ルールを実行する際は、makeコマンドの後にルール名を指定します。

```shell
make all
```

ルール名を省略した場合は、一番上のルールが実行されます。そのため、allのルールは一番上に書いておくとよさそうです。



また、`all`、`clean`、`fclean`、`re`の4つは多くのMakefileでよく記述されるルールですので、記述しておいたほうがよいです。

それぞれ、下記のような意味になります。

| ルール名 | 意味                                                         |
| -------- | ------------------------------------------------------------ |
| all      | すべてのソースコードをコンパイルする                         |
| clean    | コンパイル時の中間ファイルを削除する                         |
| fclean   | cleanに加え、実行可能ファイルも削除する                      |
| re       | すべてのソースコードを再コンパイルする<br />fclean、allを実行するのと同じ |



# ミニマムMakefile

ここまでの文法を踏まえ、CおよびC++用で最低限記述しておいたほうがよいものをすべて含めたMakefileを示します。

## C言語用

```makefile
NAME		= myprogram
SRCS		= myprogram.c
OBJS		= $(SRCS:.c=.o)
CC			= gcc
CFLAGS		= -Wall -Wextra -Werror

.PHONY: all
all: $(NAME)

$(NAME): $(OBJS)
	$(CC) $(CFLAGS) -o $(NAME) $(OBJS)

.PHONY: clean
clean:
	$(RM) $(OBJS)

.PHONY: fclean
fclean: clean
	$(RM) $(NAME)

.PHONY: re
re: fclean all
```



## C++用

```makefile
NAME		= myprogram
SRCS		= myprogram.c
OBJS		= $(SRCS:.cpp=.o)
CXX			= clang++
CXXFLAGS	= -Wall -Wextra -Werror

.PHONY: all
all: $(NAME)

$(NAME): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)

.PHONY: clean
clean:
	$(RM) $(OBJS)

.PHONY: fclean
fclean: clean
	$(RM) $(NAME)

.PHONY: re
re: fclean all
```



# おすすめルール

上記のミニマムMakefileでも十分使えますが、便利機能をつけておくと色々とはかどりますのでご紹介します。

## ソースファイルの自動列挙

ソースファイルを追加した際、SRCS変数にソースファイル名を毎回記述するのは面倒です。シェルのfindコマンドで指定した拡張子のファイルを毎回自動的に列挙するようにします。

```makefile
SRCS		= $(shell find $(SRCDIR) -name "*.cpp" -type f | xargs)
```



注意点として、実験用ソースファイルなど、プログラムに含めたくないソースファイルを同じディレクトリに置いてしまうとそれもSRCS変数に含めてしまいますので気を付けてください。



## ヘッダファイル修正によるリコンパイル

.cや.cppファイルを修正した場合は、ファイルのタイムスタンプで修正があったかどうかを判定できます。ただ、それらのソースコード内で読み込んでいるヘッダファイルを更新した場合、ソースが変更されたかを認識できません。

これを回避して、ヘッダファイルを更新しても自動判定してくれるようにします。

```makefile
DEPENDS		= $(OBJS:.o=.d)
CXXFLAGS	+= -MMD -MP

-include $(DEPENDS)

.PHONY: clean
clean:
	rm -f $(OBJS) $(DEPENDS)
```

1. `CXXFLAGS`に `-MMD`オプションを追加して、依存関係を.dファイルに出力します。
   このオプションでは、システムヘッダファイルを除いた、ユーザ定義のヘッダファイルの依存関係のみが.dファイルに出力されます。
2. `-MP`オプションを指定して、依存関係に加えて、ヘッダファイルのターゲットも出力します。
3. `-include $(DEPENDS)`で、出力した依存関係をこのMakefileに組み入れます。頭にハイフンをつけて、初回実行時などでファイルがない場合でも先に進むようにします。
4. `make clean`ルールで`.d`の依存関係ファイルを削除するようにします。



## 他のGitリポジトリをcloneする

git cloneするルールを記述しましょう。

```makefile
CLONE_DIR	= minilibx-linux
GIT_URL		= https://github.com/42Paris/minilibx-linux

$(CLONE_DIR):
	git clone $(GIT_URL) $(CLONE_DIR)
```



## デバッグモードの追加

デバッグ用にコンパイルすることがある場合、debugルールを記述しておくと便利です。

```makefile
.PHONY: debug
debug: CXXFLAGS += -g -fsanitize=integer -fsanitize=address -fsanitize=leak
debug: re
```



## C/C++のバージョン指定

特定のC/C++バージョンを指定する場合は、CFLAGS/CXXFLAGSに下記を追加します。指定したバージョン以外の記述をコンパイルエラーにする場合は、`-pedantic-errors`もあわせて指定します。エラーではなく単に警告にする場合は`-Wpedantic`とします。

例：C++98/03準拠コード以外をエラーにする場合

```makefile
 CXXFLAGS	+= -std=c++98 -pedantic-errors
```



# これが最強のMakefileだッ！

おすすめルールをいくつか取り込んだ、どのプロジェクトでも汎用的に使えるC++用最強のMakefileがこちらになります。

## C++用

```makefile
NAME		= myprogram
SRCDIR		= ./src
SRCS		= $(shell find $(SRCDIR) -name "*.cpp" -type f | xargs)
OBJS		= $(SRCS:.cpp=.o)
DEPENDS		= $(OBJS:.o=.d)
CXX			= clang++
CXXFLAGS	= -Wall -Wextra -Werror -std=c++98 -pedantic-errors -MMD -MP

.PHONY: all
all: $(NAME)

-include $(DEPENDS)

$(NAME): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)

.PHONY: clean
clean:
	rm -f $(OBJS) $(DEPENDS)

.PHONY: fclean
fclean: clean
	rm -f $(NAME)

.PHONY: re
re: fclean all

.PHONY: debug
debug: CXXFLAGS += -g -fsanitize=integer -fsanitize=address -fsanitize=leak
debug: re
```



# FAQ

## 変数の代入は := と = のどっちがいいか？

単純展開変数`:=`と再帰展開変数 `=`では、値を再代入したときの動作が少し違います。

再帰展開変数`=`のほうが動作的には自然かなと思います。

- 単純展開変数 `:=`
  最初に代入した値で確定され、再代入しても無視されます。

```makefile
FOO := foo
BAR := $(FOO)
FOO := baz

all:
	@echo BAR: $(BAR)        # BAR: foo
```

- 再帰展開変数 `=`
  再代入すると値が書き換わります。

```makefile
FOO = foo
BAR = $(FOO)
FOO = baz

all:
	@echo BAR: $(BAR)        # BAR: baz
```



# Tips

## どのコマンドが動いているのか分からない

`-n`オプションをつけて動かすと、コマンド実行なしで、実行される予定のコマンドラインの表示のみを行えます。

```shell
make -n
```



## 実行されるコマンドの画面表示がうっとおしい

コマンドの先頭に@をつけると、コマンド行が表示されず、実行のみ行われます。

```makefile
test:
	@echo test
```



## Makefileから別のMakefileを呼ぶ

```makefile
make -C dirname
```

で、別のディレクトリ内のMakefileを実行できます。



# さいごに

とっつきづらい部分もありますが、分かってしまえばかなり便利に使えるmake。C/C++の開発を効率的に進めましょう！



明日は、@corvvsさんが三体問題について書いてくれる予定ですので、そちらの記事もお楽しみに！



# 参考URL

- 公式マニュアル

  [GNU make](https://www.gnu.org/software/make/manual/html_node/index.html)

- -MMDや-MPなどのプリプロセッサオプションについて

  [GCCの依存関係ファイル出力用のオプション](http://ogawa.s18.xrea.com/tdiary/20171121p01.html)

  [Using the GNU Compiler Collection (GCC)  3.13 Options Controlling the Preprocessor](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html)

- -std=c++98などのC/C++バージョン指定について

  [Using the GNU Compiler Collection (GCC)  3.4 Options Controlling C Dialect](https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html)

  [GCC拡張の無効化 (と、それにまつわる細かい話)](https://qnighy.hatenablog.com/entry/2015/09/26/192104)