初心者C++Advent Calendar 2017
この記事は初心者C++Advent Calendar 2017 12日目の記事です
本来もっとあとに記事を出す予定でしたがなんか空いているのでゆるふわ記事を出します。ガチな記事は12/15までお待ち下さい。
まだ初心者C++ AdC、2つ1つ空いてますね。だれかstd::string_view
について書きませんか??私は時間が足りません。
ありきたりなCMakeのプロジェクト作成
CMakeも極めればだいぶ色々できるスクリプト言語らしく、Findxxx
系のスクリプトをのぞくと毎回うわぁとなってしまいます。
がしかし多くのCMakeユーザーに必要なのはそんなガチな書き方じゃなくてもっとゆるふわな、そういう動機のはず。
先行記事
当然CMakeなんて超メジャーツール、日本語の解説がないわけがない。
- ごく簡単なcmakeの使い方: 多分いちばん有名な記事
- 中規模なC++の新しいプロジェクトを作るときにやるべきこと 2016年版: CMake含めた総合的な話
- c++14 with openmp + gtest-1.8のcmake環境を整備する: gtestを自動で拾おうとしている
- cmakeでビルド時にシェルスクリプトを実行する: カスタムターゲットについて
- CMake: モジュール: そもそもモジュールとはなんぞ
- CMake: キャッシュ変数と環境変数: 設定項目付きCMakeを書くなら必読
- たのしい組み込みCMake: CMakeのノウハウのかまたり
自分がQiitaでストックしていた記事だけでもこんなものがあった。なかでもごく簡単なcmakeの使い方
は極めて簡潔でわかりやすい。
問題点
たしかにごく簡単なcmakeの使い方
はわかりやすいんだけど、実際書こうとするともうすこし色々書かないと実用に耐えない。その辺を解説している記事がたどり着きにくく、StackOverflow何かを読まないといけない。
本題
まあまず実際に使っているプロジェクトを見るのが早いだろう。
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)
解説していく。
まずやること
プロジェクトの名前を決める。普通これがそのまま実行ファイルなりライブラリの名前になる。
これが
project(KCS_CUI)
の部分だ。以降のKCS_CUI
の部分はここで決定したものを使う。
ソースファイルの列挙
CMakeLists.txt
からみた相対パスでソースファイルを列挙する。
なお#include "xxx.h"
のようにincludeされているものについては書かなくてもCMakeがいい感じにしてくれる。つまり翻訳単位となる.cpp
ファイルが該当する。
今回は可読性の観点からkcs_kai_src
という変数に格納した。
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でコンパイルする
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
くらいはつけて使っているはずだ。
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
もつけている。
コンパイル
ではコンパイルコマンドを発行しよう。
add_executable(KCS_CUI ${kcs_kai_src})
C++11 Threadを使う
今回の対象となるソースコードはC++11で追加されたThreadを使っている。こいつを使うには例えばpthreadとリンクしないといけなかったりする。その辺をやっているのが
find_package(Threads REQUIRED)
と
target_link_libraries(KCS_CUI Threads::Threads)
だ。この合わせて2行でよきに計らってくれる。
ちょっとしたオプションを使いたい時
これも実例を見たほうが早いだろう。
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
にバグが有ってワークアラウンドとしてレポジトリにパッチを当てたものを置いており、CMakeのバージョンを見て切り替えている。
なおどういうわけかLibreSSLだとCMake側はいいのだがリンクでコケる。なんでや。
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を吐きたいことがある。実例を見よう。
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_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 "."
)
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 ".")
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 ".")
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 ".")
set(
SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/../main_prog.cpp"
)
add_definitions(-DUSE_STDLIB_QSORT)
add_executable(qsort ${SRCS})
install(TARGETS qsort RUNTIME DESTINATION ".")
$ 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
.vcxproj
でそれを追加して認識させるためには
<ItemGroup>
<Natvis Include="inferior_vector.natvis" />
</ItemGroup>
のようなものが書かれていれば認識されます。ではCMakeからこれを吐き出すには?
add_executable(<project name> <source files>... <natvis files>...)
単にadd_executable
に指定してあげればいいようです(Thanks @Chironian )。順番はどうでもいいです。
VS以外には単に無視されるのでif(MSVC)
とか書く必要はないです。
ちなみにCMake3.7.0からなので、
cmake_minimum_required(VERSION 3.7.0)
しましょう。
CMakeの、もしくは付属のFindxxx.cmake
にバグを見つけた時
Let's contribute!
GithubにもCMakeのレポジトリがありますが、ただの案山子なのでGitLabのほうを見に行きましょう。
このへんよく読んでMerge Requestを投げつけましょう。ローカルにgit hock入れさせられます。
自分が過去に投げてMergeされたやつ↓
FindBoost: Fix release name candidate list construction (!673) · Merge Requests · CMake / CMake · GitLab
C#周りはまだバグが多そうな印象なので(適当)チャンスですね。
@Chironian 氏が先月あたり
CSharp: fix generation fail in environment except English (!1449) · Merge Requests · CMake / CMake · GitLab
投げてましたね。
CMakeでライブラリをビルドするとき
Findxxx系を作って提供するのは時代遅れになりつつあり、xxx-config.cmakeを自動生成させるようにするべきです。
このときcmake2.8.12以降を使い、またcmake_minimum_required
もそのようにしましょう(さもないとCMP0022が出る)。
詳細は
お手軽な xxx-config.cmake の作成方法
がよくまとまっているので丸投げします。