1. shrhdk

    完結→簡潔

    shrhdk
Changes in body
Source | HTML | Preview
@@ -1,525 +1,525 @@
# 初心者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を書くなら必読
-自分がQiitaでストックしていた記事だけでもこんなものがあった。なかでも`ごく簡単な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)
がよくまとまっているので丸投げします。