【初心者向け】0から分かるMake・CMake

# はじめに
MakeやCMakeは様々なプロジェクトで使われている，`c/c++`コードのコンパイルを手助けしてくれる基盤技術です．
でも初心者には取っつきにくいですよね．チュートリアル通りにやったらなんか上手くいったけど，何やってるかよく分かんない！だから自分なりに変更できないし，エラーが出ても対処できない…ってことありませんか？（私はこれまでずっとそうでした…）
個人的にプロジェクトを立ち上げるにあたりMakeやCMakeについて初めてちゃんと知ったので，備忘録をかねてまとめておきます．同じ状況の初心者の助けになればと思います．

# Makeとは？なぜ使うの？
ソースファイルが1つだけの時，そのコンパイルはごく簡単です．
```bash
g++ main.cpp -o main
```
これで`main`という実行ファイルが生成されます．

では次のようにもっとたくさんのファイルがある時にはどうなるでしょう？
- ソースファイル
  - `main.cpp`
  - `file1.cpp`
  - `file2.cpp`
- ヘッダファイル
  - `file1.hpp`
  - `file2.hpp`

この場合，コンパイルのコマンドは次のようになります．
```bash
g++ -c main.cpp -o main.o
g++ -c file1.cpp -o file1.o
g++ -c file2.cpp -o file2.o
g++ main.o file1.o file2.o -o main
```
ちょっと面倒ですねー…ファイルが増えたりコンパイラオプションを追加したりすることになったら，もっと面倒です．

そこで活躍するのがMakeです．上記のファイル一式をMakeでコンパイルにはどうすればいいのか見てみましょう．Makeではまず`Makefile`いう名前のファイルを用意し，その中に次のように記載します．
```Makefile
SRC = main.cpp file1.cpp file2.cpp # source files
OBJ = $(SRC:.cpp=.o) # object files

CXX = g++ # compiler
CXXFLAGS = -Wall -std=c++11 # compile options (flags)

main: $(OBJ)
  $(CXX) $(CXXFLAGS) -o main $(OBJ) # command to generate exe file

# How the object files are related
main.o: main.cpp file1.hpp file2.hpp
file1.o: file1.cpp file1.hpp
file2.o: file2.cpp file2.hpp

# clean
clean:
  rm -f $(OBJ) main
```
ここで中身を理解する必要はありませんが，大体の感じは見て分かると思います．設定を記述すれば，Makeがコンパイルのための複雑なコマンドを生成してくれるのです．
`Makefile`が作成できたら，次のコマンドを打ちます．
```bash
make
```
これだけです．ずいぶん楽になりましたね．

Makeを使うことの利点は次のようにまとめられます．
- 複雑なコンパイルコマンドを入力する必要がない
- ファイルが追加された場合は`Makefile`に追記すればいい
- コンパイルオプションを変更する場合は`Makefile`を変更すればいい

:::note
加えて，Makeには変更が加えられたファイルのみを再コンパイルしてくれます．従ってたくさんのファイルがある場合には普通にコンパイルするよりも速く済むという大きな利点もあります．
:::

ちなみに，このようにコンパイルの手助けをしてくれる仕組みのことを（特にCMakeの文脈で）ビルドシステムと呼びます．CMakeのほかに，MSBuild（Microsoft Build engine）やNinjaもビルドシステムの一種です．

# CMakeとは？なぜ使うの？
これまでMakeを使うとコンパイルが楽になることを見てきましたが，Makeで十分コンパイルが楽になるならなぜCMakeも必要なのでしょうか？

実はMakeのみを使ったプロジェクトには次のような懸念事項があるんです．
- 世の中には他にもビルドシステムがあり，それぞれに適した設定ファイル（Makeでいう`Makefile`）が必要です．提供するプロジェクトの他のユーザーがMakeではなくMSBuildやNinjaを使いたい場合には，それぞれに適した設定ファイルを用意する必要があります．
- ビルドシステムの設定ファイルは複雑で，独特な記法を採用していることが多いです．上記の`Makefile`の例も初見じゃよく分かんないですよね．

特に最初の点はチーム開発の際に問題となります．だってファイル追加するだけで全部のビルドシステム用の設定ファイルを書き換えるわけにいかないですよね．

CMakeはこういった問題を解決してくれます．CMakeの機能を端的にいえば「ユーザーが指定したビルドシステムが使う設定ファイルを用意する」ことです．例えばユーザーがMakeを使いたいならCMakeは`Makefile`を生成してくれるということですね．

CMakeを使うには，`CMakeLists.txt`という設定ファイルを用意します．その中身は先ほどのプロジェクトの例では次のようになります．
```txt
cmake_minimum_required(VERSION 3.10)

project(MyProject VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_executable(main main.cpp file1.cpp file2.cpp)
```
さっきの`Makefile`に比べてめっちゃ簡単になってますよね！

`CMakeLists.txt`が準備できたら，次のコマンドを打ちます．
```bash
cmake -S . -B build
```
引数`-S`は`path-to-source`を意味しており，`CMakeLists.txt`を含むソースファイル一式が含まれたディレクトリへのパスを指定します．`-B`は`path-to-build`を意味しており，ビルドシステム用の設定ファイルが生成される場所を指定します．上記のコマンドを打てば，`build/Makefiles`が生成されるわけです．
ちなみに，よく次のように紹介されることもありますが，これも同じことをしています．
```bash
mkdir build
cd build
cmake ..
```
この場合だと一度`build`ディレクトリを生成し，そこに移動してから，1階層上の`CMakeLists.txt`を参照しています．先述の方法の方が端的で良いと思うのですが，CMakeのバージョンが古い場合には`-S`や`-G`を使うことができず，このやり方しかできないみたいです．

どのビルドシステムを使うかは，引数`-G`で指定します．`-G`はビルドシステムへの設定ファイルを生成する役割を持つ`CMake Generator`を意味しています．例えば，
```bash
cmake -S . -B build -G "Unix Makefiles"
```
はMakeが使う`Makefile`を生成しますし，
```bash
cmake -S . -B build -G "Visual Studio 17 2022"
```
はMSBuildが使うVisual Studio 2022のプロジェクトファイル一式を生成しますし，
```bash
cmake -S . -B build -G "Ninja"
```
はNinjaが使う`build.ninja`ファイルを生成します．ご自身の環境でどのビルドシステムが使えて，`-G`に何を渡せばいいかは，`cmake --help`で確認できます．

:::note
引数`-G`は任意です．`-G`が渡されなかった場合，CMakeはプラットフォームの状態に応じて決められたデフォルトのビルドシステムを採用します．現在のデフォルトは`cmake --help`で確認できます．
:::

CMakeを導入すれば，
- 設定ファイル（`CMakeLists.txt`）の記述が楽になる
- プロジェクトをプラットフォームに依存しない形にできる

ということがお分かり頂けたのではないでしょうか．`CMakeLists.txt`ファイルさえ作ってしまえば，ユーザーがどのOS，どのビルドシステムを使っていたとしても対応できちゃうんです！

# ワークフロー
では最後にCMakeとMakeを使ったコンパイルの手順を確認しておきましょう．最終的なコマンドは次のようになります．
```bash
cmake -S . -B build -G "Unix Makefiles" # build/Makefileを生成
cd build # buildディレクトリへ移動
make # 生成されたMakefileを使って実行ファイルを生成
```

もっと簡単に，こんな風にコンパイルすることもできます．
```bash
cmake -S . -B build -G "Unix Makefiles"
cmake --build build
```
この場合Makeは呼び出しませんでしたが，`cmake --build build`によってCMakeが自動的にMakeを呼び出し，実行ファイルの生成までやってくれます．

# 参考

https://cmake.org/cmake/help/latest/