C++erはCMakeを崇拝しろ + おまけでAzurePipelinesでビルド自動化

やや遅れてしまいましたが、
この記事は、[C++ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/cpp) 14日目の記事です
次日の記事は@tyanmahouさんです

## この記事の対象者
`C++開発環境の改善をしたい人`
`CMake分かんねぇという方`
`複数プラットフォームのビルド環境のメンテに疲れた人`
`C++のクロスプラットフォーム開発をしたい人またそれをもっと効率化したい人`
`ビルドの自動化したい方`
`VisualStudioに疲れてきた人`
`VisualStudioからCMakeに移行したいという方`
あてはまらない方でも何かの参考になれば嬉しいです。

## 更新履歴
- 2020/12/27 
  - [プロジェクトの参照パスを相対参照にしたい](#プロジェクトの参照パスを相対参照にしたい)の項にバージョン依存に関する説明を追加
  - [VisualStudio、Xcode用のプロジェクトを生成する](#VisualStudio、Xcode用のプロジェクトを生成する)の項のWindowビルドに関して記述誤りを修正


<!--
## 初めに...
[なぜC++なのか...](https://tk3118.hatenablog.com/entry/2019/12/15/011215?_ga=2.234571357.218525139.1576308816-549724786.1575612075)という疑問を持つ方がいればこちらを参照してください。
-->

## C++の問題点
周りのプログラマーに聞いても、C++書きたくないって人が大多数だったので理由をヒアリングしてきました。

`-  C++のココが無理`

1. メモリ管理が完全自己責任(メリットでもある)
2. cpp/hファイル両方書くのめんどくさい
3. ビルドキツすぎる
4. クロスプラットフォーム開発が闇
5. 上記以外の諸々の理由から生産性が低い

ヒアリング内容に加えて著者の意見も入っていますが、まあこんなところでしょうか？  
`他にもあるだろ！これは言いたい！！`っていうのがあれば、コメントにてお願いします！！

3,4の問題に関してこの記事で書いていきます。
他はまた別の記事で書いていこうと思います。
`ビルドキツすぎる、クロスプラットフォーム開発が闇`
この問題は主にプラットフォームによってビルドの方法が違ったりすることが主な理由で、まずそれぞれのプラットフォームそれぞれのやり方に合わせると学習コストがかかるのもそうですが、最大の問題点は運用コストです。
例えばOSSのC++ライブラリを自分でビルドした時に、こっちだと通らなかったけど
こっちだと通った！という経験などないでしょうか？
これをちゃんとやろうとすると
普通に考えてプラットフォーム数分の対応コストがかかる訳でめんどい以外の何ものでもないですよね？
そこで`CMake`の登場です！(CMakeに関しては後述します)
CMakeを使えばCMakeのファイルを用いて複数のプラットフォーム用にビルドファイルを生成できるので、無駄だった運用コストが無くなります。
そういった理由から、CMakeの対応がなされているOSSが多くなってきているのだと思います。

もうちょっとビルドの闇を知りたいという方は、 
ついでにこの記事もどうぞ、[さようならVisualStudio...はじめましてCMake!]()

## CMakeをはじめる
↑で述べてきた理由からCMakeを始めていきたいと思います。
CMakeの記事は、割とどこも断片的な記事が多いので、この記事でしっかりまとめていけたらと思います。
これの説明が欲しい！ここもっと詳しく！！などがあれば対応していこうと思いますのでよろしくお願いします。

### CMakeとは？
[CMake.org](https://cmake.org/)

`引用: (https://ja.wikipedia.org/wiki/CMake)`

```
CMakeはコンパイラに依存しないビルド自動化のためのフリーソフトウェアであり、
様々なオペレーティングシステムで動作させることができる。
CMakeは階層化ディレクトリや複数のライブラリを利用する
アプリケーションをサポートするよう設計されている。
実際のビルドにおいては、make、Xcode、Visual Studioのようなネイティブのビルド環境が利用される。
CMake自身は最小限の依存関係を持つよう設計されており、
ビルドするにはC++コンパイラのみを必要とする
```

### CMakeでビルドを行う

`テスト環境`

- CMake - 3.15.5
- macOS High Sierra 10.13.6


#### ビルド用のプロジェクト作成を行う
※cmakeにパスが通っていない場合はしっかり通してからはじめてね！

プロジェクト直下でコマンドを開いて、以下のコマンドを入力します
これを行うことでビルドするためのプロジェクトの作成を自動で行ってくれます。

```
$ mkdir build
$ cd build
 # CMakeList.txtがプロジェクト直下にある場合.
$ cmake ..
```

`Point`

```
そもそもなんでこの`build`フォルダ作るの？という話ですが、
無くても出来ます。ちなみにそれを`in-source`ビルドと言います。 
今回やっている方法は、`out-of-source`ビルドといいます。 
これに関してはこちらなどを見てみると良いでしょう。https://qiita.com/osamu0329/items/7de2b190df3cfb4ad0ca

要は、プロジェクトとコードは分けたほうがいいし、
プロジェクト消す時もbuildフォルダ消すだけで良いので便利じゃない？って話です。
```

#### VisualStudio、Xcode用のプロジェクトを生成する

VisualStudio、Xcode用のプロジェクトを生成したいという方は  
上記cmakeコマンド部分を以下のように置き換えることで簡単に行えます。
CMake神！

- ex) Visual Studio 2017の場合

```
$ cmake -G "Visual Studio 15 2017" ..

# x64でビルドしたい場合はコッチ
$ cmake -G "Visual Studio 15 2017 Win64" ..
```

- xcodeの場合

```
$ cmake -G Xcode ..
```

ちなみにCMakeで使えるジェネレータ一覧は[こちら](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#id5)を参照

以下参考程度に...  
`Windows - CMake 3.9.0 でヘルプを出した場合`

```
Generators
  Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files. Optional [arch] can be "Win64" or "ARM".
  Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.　Optional [arch] can be "Win64" or "ARM".
  Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.　Optional [arch] can be "Win64" or "ARM".
  Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.　Optional [arch] can be "Win64" or "ARM".
  Visual Studio 10 2010 [arch] = Generates Visual Studio 2010 project files.　Optional [arch] can be "Win64" or "IA64".
  Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.　Optional [arch] can be "Win64" or "IA64".
  Visual Studio 8 2005 [arch]  = Deprecated.  Generates Visual Studio 2005　project files.  Optional [arch] can be "Win64".
  Borland Makefiles            = Generates Borland makefiles.
  NMake Makefiles              = Generates NMake makefiles.
  NMake Makefiles JOM          = Generates JOM makefiles.
  Green Hills MULTI            = Generates Green Hills MULTI files
                                 (experimental, work-in-progress).
  MSYS Makefiles               = Generates MSYS makefiles.
  MinGW Makefiles              = Generates a make file for use with
                                 mingw32-make.
  Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Watcom WMake                 = Generates Watcom WMake makefiles.
  CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles JOM
                               = Generates CodeBlocks project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - MinGW Makefiles   = Generates CodeLite project files.
  CodeLite - NMake Makefiles   = Generates CodeLite project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Sublime Text 2 - MinGW Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - NMake Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.
  Kate - MinGW Makefiles       = Generates Kate project files.
  Kate - NMake Makefiles       = Generates Kate project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Eclipse CDT4 - NMake Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - MinGW Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
```

`MacOS - CMake 3.15.5 でヘルプを出した場合`

```
Generators
* Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Xcode                        = Generate Xcode project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.

```

#### プロジェクトの参照パスを相対参照にしたい
※ <font color="Red">CMake3.4から使えなくなっているので注意!!</font>

CMakeでビルドしたプロジェクトを別のディレクトリに動かしたら
絶対パスだったから参照が死んだ！といった経験ないでしょうか？
(Visual Studioとかでビルドしてるとなるはず...)
その場合、もう一回ビルドしたり、あえなく元のフォルダに置いておくことを余儀なくされているでしょうか？
自分もそのようなことしていましたが、

```
# VS2017の場合
$ cmake  -DCMAKE_USE_RELATIVE_PATHS=ON -G "Visual Studio 2017 Win64" ..
```
↑のようにコマンドを入力することで相対パスでプロジェクトが生成されます。CMake神!

#### ビルドしてみる
[ビルド用のプロジェクト作成を行う]()の段階まで終わっていれば、  
以下のコマンドを入力することでビルドを行ってくれます。

```
$ cmake --build ./ --target install # make install と同じ
```

※ 補足  
`make install`、 `msbuild`、`xcodebuild`などをCMake内部で解決してくれます。神!!



### CMakeのビルドファイルを作成する 
CMakeは基本的に`CMakeLists.txt`というファイルを作成して記述していきます。
書き方的には、一種のシェルスクリプトのようなものだと覚えておけば大丈夫でしょう。
`CMakeLists.txt`の書き方を章ごとに分けて説明していきたいと思います。
なお`章ごとに新しい機能に関してはコメントを追加`していきます。

また書き方を参考にする際、Qiitaやブログなどを検索することも大事ですが、実際に運用されているものを見たほうが勉強になることもあります。実際自分は[glfw](https://github.com/glfw/glfw/blob/master/CMakeLists.txt)などを参考にしたりしました。

#### 最小限のビルドファイルを作成する

```CMakeLists.txt
# cmakeの最低保証バージョンを指定(ここでは3.2を指定している).
cmake_minimum_required(VERSION 3.2)
　
# プロジェクト名をTestに設定.
project(Test)           
　
# main.cppから実行ファイルをTestAppとして作成.
add_executable(
    TestApp
    src/main.cpp
)
```

- `cmake_minimum_required()` こちらを使ってCMakeの最低バージョンを指定します。特に分からない場合自分のCMakeのバージョンを `$cmake --version`から確認して、コピペすると良いでしょう。主にバージョンによってサポートされていない構文があるので、その為に使うぐらいの認識で大丈夫でしょう。

- `project()`を使用するとプロジェクト名が設定できますVisualStudioで言うところのソリューションファイル名になります。

- `add_executable()`実行ファイルを作成できます。add_executableを複数書くことも出来てその場合、その数分アプリケーションを生成します。  
※ ヘッダーファイルは参照パスに入っていれば自動で解決してくれるので、ソースファイルのみの指定で大丈夫です。

#### 複数ファイルをコンパイルする

```CMakeLists.txt
add_executable(
    TestApp
    Source/main.cpp
    Source/hoge.cpp   # hoge.cppをコンパイル対象に追加する
)
```
- add_executableに追加することで↑のように足していくだけです。

#### ライブラリとして作成する

```CMakeLists.txt
add_library(
    TestApp
    src/main.cpp
)
```
- `add_executable`と同じように記入します。呼ぶ出す関数がadd_libraryに変わっただけです。

ちなみに共有ライブラリの場合は以下です。

```CMakeLists.txt
add_library(
    TestModule
    SHARED  # SHAREDを指定します
    src/main.cpp
)
```

静的ライブラリの場合は以下です。

```CMakeLists.txt
add_library(
    TestModule
    STATIC  # STATICを指定します
    src/main.cpp
)
```

#### ライブラリを導入する
外部のライブラリを使うときにどうしたらよいのかという話です。
※ このあたりから力尽きたので、若干手抜きになってます。

##### インクルードディレクトリの追加

```CMakeLists.txt
# Boostのパスを設定する
include_directories("${BOOST_DIR}")
```

##### ライブラリの追加

```CMakeLists.txt
target_link_libraries()
```
- `target_link_libraries()`を使う。SHAREDもSTATICもこれで設定出来ます。

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

```CMakeLists.txt
# C++11を指定する
add_definitions(-std=c++11)
```

#### コンパイラオプションを設定する

```CMakeLists.txt
set(CMAKE_CXX_FLAGS "-02 -Wall")
```
- `CMAKE_CXX_FLAGS`にコンパイラごとのオプションを設定します。

#### 変数を設定する/追加する
前節で行っている`set()`を使うことで独自の変数を作ったり、既存の変数に対しても設定を追加したり出来ます。

- ex) 例として変数を用いて`include_directories`を設定します。

```
set(INCLUDE_DIR ${BOOST_DIR})
include_directories("${INCLUDE_DIR}")
```

#### CMakeLists.txtを複数ファイルに分割する

```CMakeLists.txt
add_subdirectory(Source/Common)
```
- ここでは、`Source/Common`フォルダをサブディレクトとして指定しています。その為`Source/Common/CMakeLists.txt`の内容を追加して参照します。

#### 複数プラットフォームで処理を分ける

- ex) プラットフォーム毎に変数の設定を変更する例です。

```CMakeLists.txt
if (APPLE)
  set(PLATFORM_NAME Apple)
elseif (WIN32)
  set(PLATFORM_NAME Windows)
elseif (LINUX)
  set(PLATFORM_NAME Linux)
end
```

↓公式では、こちらの書き方が推奨されているようです。
> [Generator Expressionsを使ってtarget_compile_options/target_compile_features/target_compile_definitionsの中で条件分岐を行うことが推奨されています。](https://qiita.com/shohirose/items/637f4b712893764a7ec1)

```
target_compile_options( hoge 
  # Macの場合
  $<$<PLATFORM_ID:APPLE>:
    # ...
  >
  # Microsoft Visual Studioの場合
  $<$<CXX_COMPILER_ID:MSVC>:
    # ...
  >
  )
```
- [PLATFORM_IDに関して](https://cmake.org/cmake/help/v3.5/variable/CMAKE_SYSTEM_NAME.html#variable:CMAKE_SYSTEM_NAME)
- [COMPILER_IDに関して](https://cmake.org/cmake/help/v3.5/variable/CMAKE_LANG_COMPILER_ID.html#variable:CMAKE_%3CLANG%3E_COMPILER_ID)

### 分からなければ...

私自身CMakeを真面目に使いはじめて日が浅いので、
ほぼメモみたいなものだと思ってください。
その為、なるほど分からんという場合はコメントなど頂ければ幸いです。

あとはここをみると良いでしょう。幅広く書いてあって分かりやすくまとまっています。

- [CMakeの使い方（その１）](https://qiita.com/shohirose/items/45fb49c6b429e8b204ac)
- [CMakeの使い方（その２）](https://qiita.com/shohirose/items/637f4b712893764a7ec1)


## 参考資料

- https://qiita.com/shohirose/items/45fb49c6b429e8b204ac
- https://qiita.com/shohirose/items/637f4b712893764a7ec1
- https://engineering.linecorp.com/ja/blog/cross-platform-development-environment-cmake/
- http://mickey-happygolucky.hatenablog.com/entry/2016/11/18- 

## おまけ (AzurePipelinesでビルドを自動化する)

### Azure Pipelinesとは...

`引用: (Google)`

```
Azure Pipelinesとは、
Azureのクラウド上にホスティングされているCI/CDのサービスです。 
Azure Pipelines上でコードのビルドやテスト、
ビルドした成果物のリリースまでを自動化できます
```

ここの説明が分かりやすいです > https://news.mynavi.jp/article/zeroazure-28/

要は、Web上でGitHubなどと連携して自動でビルドを行ってくれます。
しかも`無料でも10スレッドまで使えるので高速`です。

### ビルドしてみる
`CMakeLists.txt` でちゃんとビルドできる状態であれば、
`azure-pipelines.yml`に以下のように記入することでビルドさせることが出来ます。

- 今回のケースでは WindowsとMacOSでのビルドを行っています。
  - [vmImage](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops)一覧

```azure-pipelines.yml
# azure-pipelines.yml

trigger:
- master

jobs:
  - job: macos_build
    displayName: macos build
    pool:
      vmImage: 'macos-latest'
    steps:
    - task: CMake@1
      displayName: 'cmake configure'
      inputs:
        cmakeArgs: >
            -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory)
            $(Build.SourcesDirectory)
    - task: CMake@1
      displayName: 'cmake install'
      inputs:
        cmakeArgs: >
             --build ./
             --target install

  - job: windows_build
    displayName: windows build
    pool:
      vmImage : windows-latest
    steps:
    - task: CMake@1
      displayName: 'cmake configure'
      inputs:
        cmakeArgs: >
            -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory)
            $(Build.SourcesDirectory)
            
    - task: CMake@1
      displayName: 'cmake install'
      inputs:
        cmakeArgs: >
             --build ./
             --target install

```

### 成果物を受け取る

上の状態だとまだ成果物が受け取れない状態だと思います。(ただビルドするだけ...)
成果物を受け取るには `PublishBuildArtifacts` を使用します。
ymlには、以下のような記入します。

```publishBuildArtifacts.yml
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: drop'
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'
```

### まとめ

最終的には、以下のようなymlを記入するだけで、
web上で複数プラットフォームのビルドをしてくれます。

```azure-pipelines.yml
# azure-pipelines.yml

trigger:
- master

jobs:
  - job: macos_build
    displayName: macos build
    pool:
      vmImage: 'macos-latest'
    steps:
    - task: CMake@1
      displayName: 'cmake configure'
      inputs:
        cmakeArgs: >
            -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory)
            $(Build.SourcesDirectory)
    - task: CMake@1
      displayName: 'cmake install'
      inputs:
        cmakeArgs: >
             --build ./
             --target install
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: download'
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'


  - job: windows_build
    displayName: windows build
    pool:
      vmImage : windows-latest
    steps:
    - task: CMake@1
      displayName: 'cmake configure'
      inputs:
        cmakeArgs: >
            -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory)
            $(Build.SourcesDirectory)
            
    - task: CMake@1
      displayName: 'cmake install'
      inputs:
        cmakeArgs: >
             --build ./
             --target install

    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: download'
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'


```

### 参考資料(Azure)
- https://news.mynavi.jp/article/zeroazure-28/
- https://nkshigeru.hatenablog.com/entry/2019/01/22/120000

### 追記
途中で力尽きて最後の方は説明はしょってますが、
azure-pipelines.yml周りとCMakeの書き方に関してはどこかでまた更新したいと思います。


## 最後に
ここまで見て頂きありがとうございましたmm
皆さんの開発のお力に少しでもなれていれば幸いです。