CMakeスクリプトを作成する際のガイドライン

# はじめに

CMakeを使い始めて半年以上経ちますが、まだまだ把握していない機能が多いです。
勉強のためYouTubeに上がっているCMakeに関する動画をいくつか見てきて、非推奨なコマンドや書き方があることがわかったので、それらをここにまとめておきます。

# 参考資料

- [CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”](https://youtu.be/eC9-iRN2b04)
- [C++Now 2017: Daniel Pfeifer “Effective CMake"](https://youtu.be/bsXLMQ6WgIk)
- [Embracing Modern CMake - How to recognize and use modern CMake interfaces by Stephen Kelly](https://youtu.be/JsjI5xr1jxM)
- [CMake Best Practice by Fujii Hironori](http://fujii.github.io/2015/10/10/cmake-best-practice/)
- [CMAKE - Introduction and best practices by Daniel Pfeifer](https://www.slideshare.net/DanielPfeifer1/cmake-48475415)
- [Effective Modern CMake](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1)

# ガイドライン

## 基本

現代的なCMakeの使い方は次の３点で表されます。

1. ターゲット(target)を基本としたビルドシステム
2. 依存関係を一箇所で指定する
3. ターゲットがそれに依存するものへコンパイルおよびリンクに関する情報を提供する

すなわち、 __`target_`で始まるコマンドを使用し、ターゲットに対する要件を指定する__ ことが基本となります。
CMakeのバージョンは2.8.12〜、実務上は3.0.0以降を指します。


## 現在は非推奨となっているコマンド

下記コマンドはターゲットに関わらず設定してしまうため使うべきではありません。

- `include_directories`
- `add_definitions`, `add_compile_definitions`, `add_compile_options`
- `link_directories`
- `link_libraries`

例えば、`include_directories`はコンパイラのファイルを探すパスに与えられたディレクトリを追加します。このコマンドを使用すると、定義したライン以降の全てのターゲットが指定したディレクトリをインクルードしてしまいます。`include_directories`の代わりに`target_include_directories`を使うようにしましょう。

```cmake:非推奨
include_directories(${MYLIB_INCLUDE_DIRS})
add_library(mylib SHARED mylib.cpp)
#このライン以降もFOO_INCLUDE_DIRSは有効
```

```cmake:推奨
add_library(mylib SHARED mylib.cpp)
target_include_directories(mylib
  PUBLIC ${MYLIB_INCLUDE_DIRS}
)
```

ただし、他のターゲットに属するディレクトリに対しては、`target_include_directories`を用いるべきではありません。すなわち

```cmake:非推奨
add_library(mylib SHARED ${MYLIB_SRC_FILES})

add_executable(myapp myapp.cpp)
target_link_libraries(myapp PRIVATE mylib)
target_include_directories(myapp PRIVATE ${MYLIB_INCLUDE_DIRS})
```

```cmake:推奨
add_library(mylib SHARED ${MYLIB_SRC_FILES})
target_include_directories(mylib PUBLIC ${MYLIB_INCLUDE_DIRS})

add_executable(myapp myapp.cpp)
target_link_libraries(myapp PRIVATE mylib)
# MYLIB_INCLUDE_DIRSをmylibのインクルードディレクトリとしてPUBLICスコープで指定すれば、
# 自動的にmylibに依存するmyappのインクルードディレクトリとなる。
```

## `CMAKE_<LANG>_FLAGS`にオプションを足さない

`CMAKE_<LANG>_FLAGS`の代わりに`target_compile_options`を使いましょう。

```cmake:非推奨
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
```

```cmake:推奨
target_compile_options(mylib PUBLIC -Wall)
```

## `CMAKE_CXX_FLAGS`や`target_compile_options`に`-std=c++11/c++14/c++17/c++20`を加えない

`CMAKE_CXX_STANDARD`（CMake 3.1以降）を使うか、`target_compile_features`に`cxx_std_11/cxx_std_14/cxx_std_17/cxx_std_20`を加えましょう（CMake 3.8以降）。

```cmake:非推奨
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
```

```cmake:推奨
target_compile_features(mylib PUBLIC cxx_std_17)
```


## 変数を使い過ぎない

`set`を使用して定義される変数には以下のような問題点があります。

- つづりを間違えやすい
- 他の文脈へリークしやすい（定義した以降のあらゆる場所で使用可能）
- 変数が依存するもののスコープが不明
- 変数の内容やその正しさがチェックされない

変数を使う必要がなくてもかける場合は使わないようにしましょう。
特にファイルをターゲットに追加する場合、変数の代わりに`target_sources`を使いましょう。

```cmake:非推奨
set(MYLIB_SRCS
  file1.cpp
  file2.cpp
  )
add_library(mylib ${MYLIB_SRCS})
```

```cmake:推奨
add_library(mylib
  PRIVATE
    file1.cpp
    file2.cpp
  )
```


## `PRIVATE`/`PUBLIC`/`INTERFACE`を適切に使う

これらのキーワードは、コマンドのターゲットおよびそのターゲットに依存するターゲットに対する必要性を表します。

|キーワード |ターゲットが必要とする|ターゲットに依存するターゲットが必要とする|
|:--------|:-----------------:|:---------------------------------:|
|PRIVATE  |:o:|:x:|
|PUBLIC   |:o:|:o:|
|INTERFACE|:x:|:o:|

ヘッダーのみのライブラリには`INTERFACE`を使います。

## if文よりもGenerator Expressionを使う

CMakeは

1. Configure - CMakeLists.txtをパースしキャッシュ変数をCMakeCache.txtに書き込む
2. Compute - 依存関係を計算する
3. Generate - ビルドファイル（Makefile等）を作成する

の三段階に分けて実行されます。
この中で、CMakeのif文（`if()`/`elseif()`/`else()`/`endif()`）はConfigure時に実行されるのに対し、Generator Expression（`$<...>`）はGenerateの直前に評価されるという違いがあります。この評価時期の違いによりCMakeが想定と異なる動作を引き起こすことがあります。

したがって、Configureをした後に評価するもの：

- `CONFIG`(Release/Debug/...)
- `TARGET_PROPERTY`
- `TARGET_POLICY`
- `COMPILE_FEATURES`
- `LOCATION`

に対してはGenerator Expressionsを使うようにします。

例えばVisual C++の場合、CMakeLists.txtをConfigureするのはCMakeLists.txtを含むフォルダを開いた時のみであり、ビルドタイプ（Release/Debug等）はデフォルトで`Debug`として評価されます：

> When you choose File | Open | Folder to open a folder containing a CMakeLists.txt file, the following things happen:
> （中略）
> - Visual Studio runs CMake.exe and generates the CMake cache for the default configuration, which is x86 Debug. ...
> 
> [CMake Projects in Visual C++: IDE Integration](https://docs.microsoft.com/en-us/cpp/ide/cmake-tools-for-visual-cpp?view=vs-2017#ide-integration)

したがって、その後にIDE上でビルドタイプを`Release`に変更しても既にキャッシュ変数として保存された`CMAKE_BUILD_TYPE`は変わらないため、CMakeスクリプト内で`CMAKE_BUILD_TYPE`を使って評価する部分は`Debug`のまま評価されてしまうということが起きます。if文でなくGenerator Expressionを使い、`CMAKE_BUILD_TYPE`を使わないようにすると、この問題を避けることができます。

ビルドタイプ（Release/Debug）に応じてファイルを変更したい場合、以下の２つは同じことを意図していますが、if文を使っている方はVisual C++で使うとビルドタイプを変えた時に適切に反映されません。

```cmake:if文
set(MYAPP_SRCS
  main.cpp
)
if (CMAKE_BUILD_TYPE STREQUAL DEBUG)
  list(APPEND MYAPP_SRCS logger_debug.cpp)
else()
  list(APPEND MYAPP_SRCS logger_release.cpp)
endif()

add_executable(myapp ${MYAPP_SRCS})
```


```cmake:GeneratorExpression
add_executable(myapp
  main.cpp
  $<IF:$<CONFIG:Debug>,logger_debug.cpp,logger_release.cpp>
)
```

## `file(GLOB)`を使わない

`file(GLOB)`は、CMakeを実行するたびに条件に合致するファイルのリストを自動的に作成する非常に便利なコマンドです。ところがこのコマンドはIDEで使う場合うまく動作しない場合があります（Visual C++等）。したがって、常にcmakeをコマンドラインから実行するような場合でない限り使うべきではありません。
ディレクトリごとにCMakeLists.txtを作成し、`add_subdirectory`と`target_sources`を使って再帰的にファイルを明示して追加しましょう。

```cmake:非推奨
# /src/CMakeLists.txt
# 子ディレクトリ内のソースファイルを再帰的に探索し変数へ追加
file(GLOB_RECURSE MYLIB_SRCS "*.cpp")
add_library(mylib ${MYLIB_SRCS})
```

```cmake:推奨
# /src/CMakeLists.txt
add_library(mylib)
add_subdirectory(dir1)
add_subdirectory(dir2)
# ... 子ディレクトリを同様に追加していく

# /src/dir1/CMakeLists.txt
# CMake-3.12まで
target_sources(mylib
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/file1.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/file2.cpp
    # 絶対パスで指定しないとエラーになるので注意
  )
# CMake-3.13以降
cmake_policy(SET CMP0076 NEW) # CMakeが自動的に相対パスを絶対パスへ変換する
target_sources(mylib
  PRIVATE
    file1.cpp
    file2.cpp
  )
```

## マクロの代わりに`function`を使う

マクロは呼び出す側のスコープにある変数を上書きしてしまいます。自身のスコープを持つ`function`を使いましょう。親ディレクトリのスコープにある変数を書き換える場合は`set(<variable> <value>... PARENT_SCOPE)`を使いましょう。

## キャッシュ変数には必ず接頭辞（prefix）を付ける

キャッシュ変数はグローバル変数です。名前の衝突を避けるために接頭辞をつけましょう。

## `CMAKE_SOURCE_DIR`を使わない

代わりに`CMAKE_CURRENT_SOURCE_DIR`、`PROJECT_SOURCE_DIR`、`<PROJECT-NAME>_SOURCE_DIR`を使いましょう。

- [Are CMAKE_SOURCE_DIR and PROJECT_SOURCE_DIR the same in CMake?
](https://stackoverflow.com/questions/32028667/are-cmake-source-dir-and-project-source-dir-the-same-in-cmake)

`CMAKE_SOURCE_DIR`はトップレベルのディレクトリを指します。異なるプロジェクトがネストしている場合、自身のプロジェクトのルートディレクトリ以外のパスを示すので使わないようにしましょう。

## 必要ない限りライブラリの種類を指定しない

ビルドするユーザーが静的・共有ライブラリを決められるようにしましょう。
グローバルオプション`BUILD_SHARED_LIBS`を使って選択することもできますが、各ライブラリごとにオプションを設定しておく方がいいです。

```cmake
option(MYLIB_BUILD_SHARED_LIBS "build mylib as a shared library" ON)

if (BUILD_SHARED_LIBS AND MYLIB_BUILD_SHARED_LIBS)
  add_library(mylib SHARED <files>...)
else()
  add_library(mylib STATIC <files>...)
endif()
```