ありきたりなCMakeのプロジェクト作成 for C++

# 初心者C++Advent Calendar 2017

この記事は初心者C++Advent Calendar 2017 12日目の記事です

[<< 11日目|autoの推論まとめ、あるいはautoは忖度などしない件](https://qiita.com/sumomoneko/items/678a05fad9e7b3be359f) || [13日目|C++の分割コンパイル時に関数のオーバーロード(void test(int)と void test(double)はどうやって区別されているか？ >>](https://qiita.com/qiitamatumoto/items/47a22cbcb8393e869761)

本来もっとあとに記事を出す予定でしたがなんか空いているのでゆるふわ記事を出します。ガチな記事は12/15までお待ち下さい。

まだ初心者C++ AdC、~~2つ~~1つ空いてますね。だれか`std::string_view`について書きませんか？？私は時間が足りません。

# ありきたりなCMakeのプロジェクト作成

CMakeも極めればだいぶ色々できるスクリプト言語らしく、`Findxxx`系のスクリプトをのぞくと毎回うわぁとなってしまいます。

がしかし多くのCMakeユーザーに必要なのはそんなガチな書き方じゃなくてもっとゆるふわな、そういう動機のはず。

# 先行記事

当然CMakeなんて超メジャーツール、日本語の解説がないわけがない。

- [ごく簡単なcmakeの使い方](https://qiita.com/termoshtt/items/539541c180dfc40a1189): 多分いちばん有名な記事
- [中規模なC++の新しいプロジェクトを作るときにやるべきこと 2016年版](https://qiita.com/m_mizutani/items/e51e9f17c1a29dbf5669): CMake含めた総合的な話
- [c++14 with openmp + gtest-1.8のcmake環境を整備する](https://qiita.com/takeshi-uchitane/items/04f558cb405245cfabea): gtestを自動で拾おうとしている
- [cmakeでビルド時にシェルスクリプトを実行する](https://qiita.com/termoshtt/items/7f7090f9d0c52bcd2630): カスタムターゲットについて
- [CMake: モジュール](https://qiita.com/mrk_21/items/ab32a83a12f5d37acc64): そもそもモジュールとはなんぞ
- [CMake: キャッシュ変数と環境変数](https://qiita.com/mrk_21/items/186439952a6665184444): 設定項目付きCMakeを書くなら必読
- [たのしい組み込みCMake](https://qiita.com/tnishinaga/items/c01dfd5f43e446653cd1): CMakeのノウハウのかまたり

自分がQiitaでストックしていた記事だけでもこんなものがあった。なかでも`ごく簡単なcmakeの使い方`は極めて簡潔でわかりやすい。

# 問題点

たしかに`ごく簡単なcmakeの使い方`はわかりやすいんだけど、実際書こうとするともうすこし色々書かないと実用に耐えない。その辺を解説している記事がたどり着きにくく、StackOverflow何かを読まないといけない。

# 本題

まあまず実際に使っているプロジェクトを見るのが早いだろう。

https://github.com/YSRKEN/KanColleSimulator_KAI/blob/master/KCS_CUI/source/CMakeLists.txt

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14) # C++14...
set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11
find_package(Threads REQUIRED)

## Set our project name
project(KCS_CUI)

set(kcs_kai_src 
	config.cpp
	exception.cpp
	fleet.cpp
	kammusu.cpp
	main.cpp
	mapdata.cpp
	other.cpp
	random.cpp
	result.cpp
	simulator.cpp
	weapon.cpp
)
if(MSVC)
  # Force to always compile with W4
  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
  endif()
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
  # Update if necessary
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-long-long -pedantic")
endif()
## Define the executable
add_executable(KCS_CUI ${kcs_kai_src})
target_link_libraries(KCS_CUI Threads::Threads)
```

解説していく。

## まずやること

プロジェクトの名前を決める。普通これがそのまま実行ファイルなりライブラリの名前になる。

これが

```cmake
project(KCS_CUI)
```

の部分だ。以降の`KCS_CUI`の部分はここで決定したものを使う。

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

`CMakeLists.txt`からみた相対パスでソースファイルを列挙する。

なお`#include "xxx.h"`のようにincludeされているものについては書かなくてもCMakeがいい感じにしてくれる。つまり翻訳単位となる`.cpp`ファイルが該当する。

今回は可読性の観点から`kcs_kai_src`という変数に格納した。

```cmake
set(kcs_kai_src 
    config.cpp
    exception.cpp
    fleet.cpp
    kammusu.cpp
    main.cpp
    mapdata.cpp
    other.cpp
    random.cpp
    result.cpp
    simulator.cpp
    weapon.cpp
)
```

CMakeの変数のガバガバさがわかる(違。

## C++14でコンパイルする

```cmake
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14) # C++14...
set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11
```

の部分だ。`CMAKE_CXX_STANDARD`に`11`とか`14`とか`17`とか指定すればいいはずだ。

`CMAKE_CXX_EXTENSIONS`を`OFF`にしておかないとGNU拡張のよく分からんのが使えてしまう。C++erならそんなことはしないはずだ。

## 警告レベルの設定

C++erなら、Visual C++なら`/W4`、GCC/Clangなら`-Wall -Wextra`くらいはつけて使っているはずだ。

```cmake
if(MSVC)
  # Force to always compile with W4
  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
  endif()
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
  # Update if necessary
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-long-long -pedantic")
endif()
```

それがこの部分だ。なおVisual Studio Clang CodeGenについては考慮していない。今回はなんとなく`-Wno-long-long -pedantic`もつけている。


## コンパイル

ではコンパイルコマンドを発行しよう。

```cmake
add_executable(KCS_CUI ${kcs_kai_src})
```

## C++11 Threadを使う

今回の対象となるソースコードはC++11で追加されたThreadを使っている。こいつを使うには例えばpthreadとリンクしないといけなかったりする。その辺をやっているのが

```cmake
find_package(Threads REQUIRED)
```

と

```cmake
target_link_libraries(KCS_CUI Threads::Threads)
```

だ。この合わせて2行でよきに計らってくれる。

# ちょっとしたオプションを使いたい時

これも実例を見たほうが早いだろう。

https://github.com/yumetodo/random_generator_iterator/blob/master/CMakeLists.txt

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14) # C++14...
set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11

## Use the variable PROJECT_NAME for changing the target name
set( PROJECT_NAME "random_generator_iterator" )

## Set our project name
project(${PROJECT_NAME})

set(SRCS "main.cpp")

if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
  # Update if necessary
  option(USE_RDRND "enable rdrnd" 1)
  option(USE_RDSEED "enable rdseed" 0)
  if(USE_RDRND)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mrdrnd")
  endif()
  if(USE_RDSEED)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mrdseed")
  endif()
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
endif()

## Define the executable
add_executable(${PROJECT_NAME} ${SRCS})
```

特に難しいことはない。オプションに**文字列を指定させたい場合以外は**単に`option`を呼び出せばいい。第一引数が変数名でそれを見て適宜条件分岐すればいい。

# Findxxx系をつかう: BoostとOpenSSL/LibreSSLを例に

次に示すCMakeは[書いたときは付属してくる`FindBoost.cmake`にバグが有って](http://yumetodo.hateblo.jp/entry/2017/04/06/141933)ワークアラウンドとしてレポジトリにパッチを当てたものを置いており、CMakeのバージョンを見て切り替えている。

なお[どういうわけかLibreSSLだとCMake側はいいのだがリンクでコケる。なんでや](https://github.com/yumetodo/boost_asio_ssl_file_dl_test/issues/6)。

https://github.com/yumetodo/boost_asio_ssl_file_dl_test/blob/support_libressl/CMakeLists.txt

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.7.2)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14) # C++14...
set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11

#
# Config options
#
option(BOOST_ASIO_SSL_FILE_DL_TEST_USE_LIBRE_SSL "Use LibreSSL instead of OpenSSL" TRUE)

#
# Set our project name
#
project(boost_asio_ssl_file_dl_test)

#
# Source files
#
set(boost_asio_ssl_file_dl_test_src
    boost_asio_ssl_file_dl_test/Source.cpp
    boost_asio_ssl_file_dl_test/downloader.cpp
    boost_asio_ssl_file_dl_test/downloader_impl.cpp
)

#
# find libraries and include
#

# find thread library
find_package(Threads REQUIRED)

# find Boost
# https://cmake.org/cmake/help/v3.7/module/FindBoost.html
if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 3.8)
    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules)
    include(FindBoost)
    message(STATUS "FindBoost: commit 9a8881c975ba8d814c39d52c370f3277afa80fda is used.")
endif()
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
set(Boost_USE_DEBUG_RUNTIME OFF)
set(Boost_DEBUG ON)
add_definitions(${Boost_LIB_DIAGNOSTIC_DEFINITIONS})
set(boost_required_components
    system date_time regex zlib bzip2 iostreams
    # thread
)
find_package(Boost 1.59 REQUIRED COMPONENTS ${boost_required_components})
if(NOT Boost_FOUND)
#   Config options
    set(BOOST_ROOT ${BOOST_ROOT} CACHE PATH "Set boost root directory" FORCE)
    set(BOOST_LIBRARYDIR ${BOOST_LIBRARYDIR} CACHE PATH "Set boost library directory" FORCE)
    message(FATAL_ERROR "Fail to find Boost")
endif()
include_directories(${Boost_INCLUDE_DIRS})

# find SSL Library
if(BOOST_ASIO_SSL_FILE_DL_TEST_USE_LIBRE_SSL)
#   find LibreSSL
    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules)
    include(FindLibreSSL)
    find_package(LibreSSL)
    if(NOT LIBRESSL_FOUND)
        message(FATAL_ERROR "Fail to find LibreSSL") # exit
    endif()
    message(STATUS "LibreSSL_INCLUDE_DIR: ${LibreSSL_INCLUDE_DIR}")
    include_directories(${LibreSSL_INCLUDE_DIR})
else()
#   find OpenSSL
#   https://cmake.org/cmake/help/v3.7/module/FindOpenSSL.html
    find_package(OpenSSL REQUIRED)
    if(NOT OPENSSL_FOUND)
        message(FATAL_ERROR "Fail to find OpenSSL") # exit
    endif()
    message(STATUS "OPENSSL_INCLUDE_DIR: ${OPENSSL_INCLUDE_DIR}")
    include_directories(${OPENSSL_INCLUDE_DIR})
endif()

#
# Update compiler waring flags
#
if(MSVC)
  # Force to always compile with W4
  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
  endif()
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
  # Update if necessary
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-long-long -pedantic")
endif()

#
# Compile
#
add_executable(boost_asio_ssl_file_dl_test ${boost_asio_ssl_file_dl_test_src})

#
# Link
#
target_link_libraries(boost_asio_ssl_file_dl_test Threads::Threads)
target_link_libraries(boost_asio_ssl_file_dl_test Boost::disable_autolinking ${Boost_LIBRARIES})
if(BOOST_ASIO_SSL_FILE_DL_TEST_USE_LIBRE_SSL)
    target_link_libraries(boost_asio_ssl_file_dl_test ${LibreSSL_LIBRARIES})
else()
    target_link_libraries(boost_asio_ssl_file_dl_test OpenSSL::SSL)
    target_link_libraries(boost_asio_ssl_file_dl_test OpenSSL::Crypto)
endif()
```

基本的に`Findxx.cmake`のたぐいはファイルを見に行くとコメントで使い方をちゃんと書いてくれているのでそれに従えばいい。というよりそれぞれ微妙に作法が違うので統一的な説明ができない。

# 少しづつ設定を変えて同じソースコードをビルドしたいとき

ベンチマークなどで、マクロの定義を差し替えて同じソースコードから複数のbinaryを吐きたいことがある。実例を見よう。

https://github.com/yumetodo/qsort

directory構成は

```
/project root
├── benchmark.sh
├── CMakeLists.txt
├── main_prog.cpp
├── mm88.h
├── mm88c.c
├── qs10a5.c
├── qs10a5.h
├── qs9e17.c
├── qs9e17.h
├── random_device.hpp
├── qs10a5
│   └── CMakeLists.txt
├── qs10a5m
│   └── CMakeLists.txt
├── qs9e17
│   └── CMakeLists.txt
└── qsort
    ├── CMakeLists.txt
    └── main.cpp
```

こんな感じです。この場合以下のように記述するとルートディレクトリで`cmake`すると全部まとめてビルドされます．

```cmake:/CMakeLists.txt
cmake_minimum_required(VERSION 3.3)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14) # C++14...
set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11

#
# Update compiler waring flags
#
if(MSVC)
  # Force to always compile with W4
  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
  endif()
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
  # Update if necessary
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-long-long -pedantic")
endif()

add_subdirectory(qsort)
add_subdirectory(qs9e17)
add_subdirectory(qs10a5)
add_subdirectory(qs10a5m)

install(
    FILES "./benchmark.sh"
    PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
    DESTINATION "."
)
```

```cmake:/qs10a5/CMakeLists.txt
set(
    SRCS
    "${CMAKE_CURRENT_SOURCE_DIR}/../main_prog.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/../mm88c.c"
    "${CMAKE_CURRENT_SOURCE_DIR}/../qs10a5.c"
)
add_definitions(-DUSE_QS10A5)
add_executable(qs10a5 ${SRCS})

install(TARGETS qs10a5 RUNTIME DESTINATION ".")
```

```cmake:/qs10a5m/CMakeLists.txt
set(
    SRCS
    "${CMAKE_CURRENT_SOURCE_DIR}/../main_prog.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/../mm88c.c"
    "${CMAKE_CURRENT_SOURCE_DIR}/../qs10a5.c"
)
add_definitions(-DUSE_QS10A5 -DMEMCPY)
add_executable(qs10a5m ${SRCS})

install(TARGETS qs10a5m RUNTIME DESTINATION ".")
```

```cmake:/qs9e17/CMakeLists.txt
set(
    SRCS
    "${CMAKE_CURRENT_SOURCE_DIR}/../main_prog.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/../mm88c.c"
    "${CMAKE_CURRENT_SOURCE_DIR}/../qs9e17.c"
)
add_definitions(-DUSE_QS9E17)
add_executable(qs9e17 ${SRCS})

install(TARGETS qs9e17 RUNTIME DESTINATION ".")
```

```cmake:/qsort/CMakeLists.txt
set(
    SRCS
    "${CMAKE_CURRENT_SOURCE_DIR}/../main_prog.cpp"
)
add_definitions(-DUSE_STDLIB_QSORT)
add_executable(qsort ${SRCS})

install(TARGETS qsort RUNTIME DESTINATION ".")
```

```sh
$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX="../bin" -DCMAKE_BUILD_TYPE=Release ..
$ make install
$ cd ../bin
```


# NatvisファイルをVSのプロジェクトに追加したいとき

Visual Studioのデバッガでの表示をカスタマイズするものとしてNatvisがあるわけですが、
[Create custom views of native objects in the debugger | Microsoft Docs](https://docs.microsoft.com/en-US/visualstudio/debugger/create-custom-views-of-native-objects)

`.vcxproj`でそれを追加して認識させるためには

```xml
  <ItemGroup>
    <Natvis Include="inferior_vector.natvis" />
  </ItemGroup>
```

のようなものが書かれていれば認識されます。ではCMakeからこれを吐き出すには？

```cmake
add_executable(<project name> <source files>... <natvis files>...)
```

単に`add_executable`に指定してあげればいいようです([Thanks](https://teratail.com/questions/114946) @Chironian )。順番はどうでもいいです。

VS以外には単に無視されるので`if(MSVC)`とか書く必要はないです。

ちなみに[CMake3.7.0から](https://gitlab.kitware.com/cmake/cmake/issues/16043)なので、

```cmake
cmake_minimum_required(VERSION 3.7.0)
```

しましょう。

# CMakeの、もしくは付属の`Findxxx.cmake`にバグを見つけた時

**Let's contribute!**

GithubにもCMakeのレポジトリがありますが、**ただの案山子**なのでGitLabのほうを見に行きましょう。

https://gitlab.kitware.com/cmake/cmake/blob/master/CONTRIBUTING.rst

このへんよく読んでMerge Requestを投げつけましょう。ローカルにgit hock入れさせられます。

自分が過去に投げてMergeされたやつ↓
[FindBoost: Fix release name candidate list construction (!673) · Merge Requests · CMake / CMake · GitLab](https://gitlab.kitware.com/cmake/cmake/merge_requests/673)

C#周りはまだバグが多そうな印象なので(適当)チャンスですね。
@Chironian 氏が先月あたり
[CSharp: fix generation fail in environment except English (!1449) · Merge Requests · CMake / CMake · GitLab](https://gitlab.kitware.com/cmake/cmake/merge_requests/1449)
投げてましたね。

# CMakeでライブラリをビルドするとき

Findxxx系を作って提供するのは時代遅れになりつつあり、xxx-config.cmakeを自動生成させるようにするべきです。

このときcmake2.8.12以降を使い、また`cmake_minimum_required`もそのようにしましょう(さもないと[CMP0022](https://cmake.org/cmake/help/latest/policy/CMP0022.html)が出る)。

詳細は
[お手軽な xxx-config.cmake の作成方法](https://qiita.com/osamu0329/items/134de918c0ffa7f0557b)
がよくまとまっているので丸投げします。

# License

[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja)

<a href="https://commons.wikimedia.org/wiki/File:CC-BY_icon.svg#/media/File:CC-BY_icon.svg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/CC-BY_icon.svg/1200px-CC-BY_icon.svg.png" alt="CC-BY icon.svg" width="88px"></a>