Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

初心者C++Advent Calendar 2017

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

<< 11日目|autoの推論まとめ、あるいはautoは忖度などしない件 || 13日目|C++の分割コンパイル時に関数のオーバーロード(void test(int)と void test(double)はどうやって区別されているか? >>

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

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

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

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

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

先行記事

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

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

問題点

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

本題

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

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

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)

解説していく。

まずやること

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

これが

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_STANDARD11とか14とか17とか指定すればいいはずだ。

CMAKE_CXX_EXTENSIONSOFFにしておかないと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行でよきに計らってくれる。

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

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

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

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にバグが有ってワークアラウンドとしてレポジトリにパッチを当てたものを置いており、CMakeのバージョンを見て切り替えている。

なおどういうわけかLibreSSLだとCMake側はいいのだがリンクでコケる。なんでや

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

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すると全部まとめてビルドされます.

/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 "."
)
/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 ".")
/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 ".")
/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 ".")
/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 ".")
$ 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のほうを見に行きましょう。

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

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 の作成方法
がよくまとまっているので丸投げします。

License

CC BY 4.0

CC-BY icon.svg

yumetodo
ありきたりなC++erです。最近C++書いていません(あれっ
http://yumetodo.hateblo.jp/
qiitadon
Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。
https://qiitadon.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした