CMake : out-of-sourceビルドで幸せになる

@osamu0329 です。
この記事は [CMake Advent Calendar 2014](http://qiita.com/advent-calendar/2014/cmake)の記事です。

3.14から追加された コマンドラインオプションの説明を[追記](#out-of-sourceビルドの方法)しました。

# はじめに

cmake を実行する際に、`cmake .` としているサンプルをよく見かけますが、これはin-sourceビルドと呼ばれるやり方です。

CMake には、前述の in-source ビルドに加えて、out-of-source ビルド (
ビルドツリーとソースツリーを分ける方法) が利用できます。

この記事では、out-of-source ビルドの方法と、

# in-sourceビルド (`cmake .`) の何が問題なのか？

まずは、in-source ビルドのどこが問題なのか、確認します。

  - git でソース管理する際に、ビルド成果物や cmakeコマンドが生成する各種ファイルによってリポジトリが汚れしまい `git status` した際に、本来追加すべきファイルを見落とす可能性がある
  - cmakeが生成したファイルをまとめて消したいときにディレクトリごとに `CMakeCache.txt` や `CMakeFiles` ディレクトリを消すのが苦痛
  - Debugビルド、Releaseビルドを分けたい時に、ソースツリーを別々に用意する必要がある

# out-of-source ビルドだと何がハッピーなのか？
まぁ、うえの裏返しです。

  - ツリーが汚れないので、まちがってビルド成果物（自動生成したファイルなど）をコミットしたりしない。ハッピー。
  - 成果物やCMakeのキャッシュを消したい場合に、ビルドツリーごとごっそり削除できる。ハッピー。
  - Debug ビルドや Releaseビルドを分けることができる。ハッピー。

# out-of-sourceビルドの方法

out-of-sourceビルドの方法は簡単です。

---- 

**(追記)** 

（ドキュメントを見る限りでは）CMake 3.13 から以下の形式で
ソースツリーとビルドツリーを指定できるようになりました。

```bash
$ cmake [<options>] -S <path-to-source> -B <path-to-build>
```

実機で確認したのは CMake 3.18 のみですが、以下の動作をします。

- ビルドツリーが存在しない場合は自動的に作成する
- ソースツリーを指定しない場合は、カレントディレクトリがソースツリーになる

そのため後述の手順は、以下のように記述できます。

```
$ ls CMakeLists.txt       # カレントディレクトリはソースツリー。（このコマンドは説明用なので実際には不要です）
CMakeLists.txt
$ cmake -B build          # ビルドツリーとしてbuildディレクトリを作成して
                          # プロジェクトファイル(Makefileとか）を生成
$ cmake --build build     # ビルドツリーの中でプロジェクトのビルド(`make` とか）を行う
```

git でクローンしてビルドするなら、以下のような形でできます。

```bash
$ git clone https://gitlab.com/cmake-example/foo
$ cmake -S foo -B foo_build
$ cmake --build foo_build
```

CMake 3.14 からcmakeコマンドのドキュメントで用途別の書式が整理されて分かり易くなっているため、
最近チェックしていない方は、再度参照してみると発見があるかもしれません。

[CMake公式 cmake(1) の最新版ドキュメント](https://cmake.org/cmake/help/latest/manual/cmake.1.html)
なお、バージョン差異で利用できない機能もあるため、利用しているCMakeバージョンをご確認ください。

---

今までのやり方。

1. ビルドツリー用のディレクトリを作成する
2. ビルドツリーのトップディレクトリに`cd` する
3. `cmake` コマンドの引数にソースツリーのトップディレクトリ(`CMakeLists.txt` が配置されたディレクトリ）を指定する
4. generatorに応じたコマンドを実行 (`make` とか `nmake` とか）
だけです。

```shell-session:out-of-sourceのビルド方法
$ git clone hoge.git     # CMake プロジェクトをクローンする。
$ mkdir hoge-debug-build # ビルドツリーを作成する
$ cd hoge-noconfig       # ビルドツリーに移動
$ cmake ../hoge          # ソースツリーを指定してcmakeを実行する
$ make                   # 生成されたMakefileを実行する（実行すべきコマンドは指定したGeneratorによる）
```

```raw:note
ちなみに私個人は、ソースツリー内にbuildというディレクトリを作ってその配下にビルドツリーを作成しています（厳密にはout-of-sourceビルドではないらしい）。
buildディレクトリは .gitconfig で除外しています。
```

### CMakeLists.txtの中で、ビルドツリー内のファイルを参照する方法
例えば、以下のユースケースで、ビルドツリー内のファイルを参照したいことがあります。

- ``configure_file()`` コマンドで生成したファイルを `install(FILES)` したい
- `generate_export_header` で生成したヘッダファイルを `include_directory()` するためにビルドツリーのディレクトリを指定したい

ビルドツリーのトップレベルディレクトリを参照するために、CMakeの変数  `${CMAKE_BINARY_DIR}` を利用できますが、用途に応じて以下の変数が利用できます。

単一ディレクトリや単一のCMakeLists.txtからなるプロジェクトで、ほかのプロジェクトのサブプロジェクトとならない場合は、CMAKE_SOURCE_DIR と CMAKE_BINARY_DIR で充分だと思います。

### ソースツリーとビルドツリーに関連する変数一覧

| 変数名  | 参照先のパス |
|:-------|:--------------|
| `CMAKE_SOURCE_DIR` | ソースツリーのトップディレクトリ |
| `CMAKE_BINARY_DIR` | 上記に対応するビルドディレクトリ |
| `CMAKE_CURRENT_SOURCE_DIR` | 現在処理中の CMakeLists.txt の配置ディレクトリ |
| `CMAKE_CURRENT_BINARY_DIR` | 上記に対応するビルドディレクトリ |
| `PROJECT_SOURCE_DIR` | 現在のプロジェクトのトップディレクトリ |
| `PROJECT_BINARY_DIR` | 上記に対応するビルドディレクトリ |
| `<name>_SOURCE_DIR` |プロジェクト `name` のトップディレクトリ |
| `<name>_BINARY_DIR` |上記に対応するビルドディレクトリ |
| `CMAKE_CURRENT_LIST_DIR` | 現在処理中の cmake ファイルの配置ディレクトリ |

#### `CMAKE_BINARY_DIR`

ビルドツリーのトップディレクトリ (cmakeコマンド実行時に指定したディレクトリに対応するディレクトリ）を参照します。

```raw:注意
プロジェクトが他のプロジェクトのサブプロジェクトの場合に、この変数を使用すると、親プロジェクトのビルドツリーのトップディレクトリを参照します。
```

#### `CMAKE_CURRENT_BINARY_DIR`
現在処理中のCMakeLists.txtが配置されたディレクトリに対応するビルドディレクトリを参照します。

#### `PROJECT_BINARY_DIR`

現在のプロジェクトのビルドツリーのトップディレクトリを参照します。

参照先のディレクトリは、簡単にいうと下記のようなロジックで決まります。
1. 現在のプロジェクトを見つける。`PROJECT_BINARY_DIR` を記述したディレクトリからルート方向にたどって 最初の`project()`コマンドが記述されているCMakeLists.txtを見つける。
2. そのディレクトリに対応するビルドディレクトリが `PROJECT_BINARY_DIR` になる。

##### PROJECT_BINARY_DIR の例
以下のディレクトリ構成を持つ、CMakeプロジェクトを例にとります。
コメント部分は、そのディレクトリのCMakeLists.txtに記述されたコマンドです。

```
main_project/    # project(main), add_subdirectory(foo_project)
  foo_project/   # project(foo), add_subdirectory(foo_dir)
    foo_dir/     # ※ここから、foo_projectを参照したい
```

`foo_dir` に配置したCMakeLists.txtから`foo_project`に対応するビルドディレクトリを参照したいケースで `PROJECT_BINARY_DIR` を用います。`CMAKE_BINARY_DIR` を使用すると、 main_project のビルドディレクトリを参照します。

#### `<name>_BINARY_DIR`

`project(<name>)` が記載されたCMakeLists.txtに対応するビルドディレクトリを参照します。
他のプロジェクトから、別のプロジェクトを参照する際に使用します。

#### `xxx_SOURCE_DIR`
上記の `xxx_BINARY_DIR` に対応する `xxx_SOURCE_DIR` の変数が定義されています。こちらは、ビルドディレクトリではなくソースディレクトリを参照します。

#### `CMAKE_CURRENT_LIST_DIR`
out-of-sourceビルドとは関係ありませんが、類似の変数なので紹介します。

現在処理中の cmake ファイル（CMakeLists.txtとは限らない）が配置されたディレクトリを参照します。
一見、 `CMAKE_CURRENT_SOURCE_DIR` と同じようですが、 `CMAKE_CURRENT_SOURCE_DIR` は、現在処理中の **CMakeLists.txt** が配置されたディレクトリを参照します。

例えば、dirA の `CMakeLists.txt` から　`dirA/cmake/common.cmake` をインクルードした場合、以下のようになります。コメントとして参照されるパスを示します。（本来は絶対パスですが、便宜上簡便化して記載しています）

```cmake:dirA/CMakeLists.txt
message(STATUS ${CMAKE_SOURCE_DIR})       # dirA
message(STATUS ${CMAKE_CURRENT_LIST_DIR}) # dirA
include(dirA/cmake/common.cmake)
```

```cmake:dirA/cmake/common.cmake
message(STATUS ${CMAKE_SOURCE_DIR})       # dirA
message(STATUS ${CMAKE_CURRENT_LIST_DIR}) # dirA/cmake
```

### その他、関連する変数

#### `CMAKE_INCLUDE_CURRENT_DIR`

`set(CMAKE_INCLUDE_CURRENT_DIR ON)` とすると、`${CMAKE_CURRENT_SOURCE_DIR}` と `${CMAKE_CURRENT_BINARY_DIR}` が自動的に `include_directies()` されます。デフォルト値はOFFです。

#### `CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE`
`set(CMAKE_INCLUDE_CURRENT_DIR ON)` とすると、`${CMAKE_CURRENT_SOURCE_DIR}` と `${CMAKE_CURRENT_BINARY_DIR}` が自動的に `INTERFACE_INCLUDE_DIRECTORIES` プロパティに追加されます。デフォルト値はOFFです。


# おわりに
out-of-source ビルドを行うと、CMakeLists.txt の記載が煩雑になる面もありますが、それ以上のメリットも多々あります。ソースの利用者がout-of-sourceビルドを選択できるように気をつかっていきたいですね。

明日は osamu0329 さんの『プロパティについて』です。