CMakeの使い方(その2)


はじめに

さてその1が意外と長くなってしまったので分割しました。
その2ではコマンドラインの場合とCMakeの場合で節を分けずに進めていきます。

ステップ4:オプション

さて、ここではビルドするときに渡すオプションを見ていきたいと思います。オプションとしてよく使うものといえば、最適化オプションOやc++11/14/17の機能を有効にする-std=c++11/14/17と、-Wall等の警告オプションでしょうか。

コマンドラインからビルドする場合、例えばこんな感じでオプションを追加しますよね。

> g++ -O2 -std=c++11 -Wall ... (その他色々付加)

CMAKE_CXX_FLAGS

さて、これに対応するCMakeのコマンドはsetで、次のようにまとめてオプションを与えることができます。

set(CMAKE_CXX_FLAGS "-O2 -std=c++11 -Wall ...")

(CMakeにある変数についてはCMake Useful Variablesを参照するのが手っ取り早いです。)

さて、ではこの場合このsetコマンドをどこに置けばいいのでしょうか。これを決めるにはCMakeにおける変数のスコープを知る必要があります。CMake language variablesによると

Variables have dynamic scope. Each variable “set” or “unset” creates a binding in the current scope:
...

  • Directory Scope
    • Each of the Directories in a source tree has its own variable bindings. Before processing the CMakeLists.txt file for a directory, CMake copies all variable bindings currently defined in the parent directory, if any, to initialize the new directory scope.

要するに、親ディレクトリで定義されていればサブディレクトリでも使用することができるということでしょう。最適化等のオプションはビルド全体を通して同じものを使用するので、プロジェクト名を定義した後かつサブディレクトリを登録する前に置けば、サブディレクトリでの処理にも同じオプションが適用されます。

実際に色々位置を変えてビルドし、make VERBOSE=1でビルドの詳細を吐き出させてみましたが、setを置いて以降でしか使われなかったので、そのオプションが必要な処理の前にくるように置かないとダメということですね。

ステップ3の例の場合、次のようになります。

/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(test_cmake CXX)
set(CMAKE_CXX_FLAGS "-O2 -std=c++11 -Wall")
add_subdirectory(src)
add_subdirectory(test)

さて、とりあえず今日はここまでです。次はビルドタイプによって異なるオプションを使う場合についてまとめます。
(忘れないうちに外部ライブラリを導入するところまでいきたい・・・)

追記:現在はCMAKE_CXX_FLAGSを使わずにtarget_compile_optionstarget_compile_featurestarget_compile_definitionsを使ってオプションを設定することが推奨されています。

cmake_minimum_required(VERSION 3.8)
project(test_cmake CXX)
add_executable(a.out main.cpp)
# 最適化・警告等のオプション
target_compile_options(a.out PUBLIC -O2 -Wall)
# C++11/14/17
target_compile_features(a.out PUBLIC cxx_std_14)
# マクロ
target_compile_definitions(a.out PUBLIC NDEBUG _USE_MATH_DEFINES)

CXX_STANDARD

CMake 3.1からはstdオプションをCMAKE_CXX_STANDARDを使って指定することができるようになりました。
CMake Documentation: CMAKE_CXX_STANDARD
Stackoverflow: How to activate c++11 in CMake?

/CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(test_cmake CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-O2 -Wall")
add_subdirectory(src)
add_subdirectory(test)

追記:CMake 3.8からはtarget_compile_featurescxx_std_98/cxx_std_11/cxx_std_14/cxx_std_17を与えてC++98/11/14/17を制御することができます。

ビルドの種類によってオプションを変える

デバッグモードでビルドしたいときなど、一々CMakeLists.txtの内容を変更するのは面倒です。そこでCMakeでは、あらかじめビルドの種類によって別々のオプションを定義しておき、ビルドするときに指定できるようになっています。例えば先ほどのCMakeLists.txtを

/CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(test_cmake CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-O1 -Wall")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Og -g")
add_subdirectory(src)
add_subdirectory(test)

とすると、CMakeを実行するときに-DCMAKE_BUILD_TYPEを指定してあげれば指定したモードのフラッグを使ってビルドしてくれます。

> cmake ..                                    # デフォルト
> cmake -DCMAKE_BUILD_TYPE=Debug ..           # デバッグ
> cmake -DCMAKE_BUILD_TYPE=Release ..         # リリース
> cmake -DCMAKE_BUILD_TYPE=MinSizeRel ..      # 最小サイズリリース
> cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..  # デバッグ情報を加えたリリース

各ファイルをコンパイルする際にCMAKE_CXX_FLAGSの後ろに各モードのフラッグが付け加えられます。複数の競合するオプションがあった場合は後の方のオプションが採用されるのでしょう。実際gccだとこうなっています↓

GCC: Options That Control Optimization

If you use multiple -O options, with or without level numbers, the last such option is the one that is effective.

また、コードのプロファイルを行うなど一時的にオプションを付加したい場合は、CMakeを実行するときにフラッグを渡して追加することができます。

> cmake -DCMAKE_CXX_FLAGS="-pg" ..

ただしこのようにする場合はフラッグの書き方を次のようにしておく必要があります。

/CMakeLists.txt
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -Wall")

こうすることによって、コマンドラインから与えられたオプションが追加されるようになります。

2018年3月8日追記:

よくCMake Useful Variables: CMAKE_BUILD_TYPEをしっかりと読んでみると

You can use these default compilation flags (or modify them) by setting the CMAKE_BUILD_TYPE variable at configuration time on the command line, or from within the "ccmake" GUI. Note! The default values for these flags change with different compilers. If CMake does not know your compiler, the contents will be empty.

となっていますね(←リファレンス・ドキュメントはしっかり読みましょう)。
試しにg++の場合何がデフォルトで設定されているのか見てみたら

  • Default:
  • Debug: -g
  • Release: -O3 -DNDEBUG
  • MinSizeRel: -Os -DNDEBUG
  • RelWithDebInfo: -O2 -g -DNDEBUG

となってました。-DNDEBUGを付けるの忘れてましたw

追記:現在はGenerator Expressionsを使ってtarget_compile_options/target_compile_features/target_compile_definitionsの中で条件分岐を行うことが推奨されています。

cmake_minimum_required(VERSION 3.8)
project(cmake_example CXX)
add_executable(a.out main.cpp)
target_compile_options(a.out PUBLIC
  $<$<CONFIG:Release>:-O3>             # Release
  $<$<CONFIG:Debug>:-O0 -g>            # Debug
  $<$<CONFIG:RelWithDebgInfo>:-O3 -g>  # RelWithDebInfo
  )
target_compile_definitions(a.out PUBLIC
  $<$<NOT:$<CONFIG:Debug>>:NDEBUG>     # Debug以外
  )

プラットフォーム・コンパイラによってオプションを変更する

使用しているプラットフォーム・コンパイラを基に、与えるオプションを自動的に変えたいことがあります。私の場合、私自身はUbuntuかつgccしか使っていないのですが、研究室の仲間はMacを使っていたりWindowsを使っていたりバラバラなので、自作ライブラリを共有する場合はプラットフォーム・コンパイラのことを考えてCMakeLists.txtを作らなければなりません。

CMakeにはプラットフォーム・コンパイラを識別するための変数が用意されているのでif...elseを使って簡単に実現できます。

if (APPLE)
...
elseif (WIN32)
...
elseif (LINUX)
...
else
...
end

追記:前節同様、Generator Expressionsを使ってtarget_compile_options/target_compile_features/target_compile_definitionsの中で条件分岐を行うことが推奨されています。

cmake_minimum_required(VERSION 3.8)
project(cmake_example CXX)
add_executable(a.out main.cpp)
target_compile_options(a.out PUBLIC
  # OSによって変えるとき
  # Macの場合
  $<$<PLATFORM_ID:Darwin>: ...>
  # Linuxの場合
  $<$<PLATFORM_ID:Linux>: ...>
  # Windowsの場合
  $<$<PLATFORM_ID:Windows>: ...>

  # コンパイラによって変えるとき
  # Microsoft Visual Studioの場合
  $<$<CXX_COMPILER_ID:MSVC>: ...>
  # gccの場合
  $<$<CXX_COMPILER_ID:GNU>: ...>
  # LLVM Clangの場合
  $<$<CXX_COMPILER_ID:Clang>: ...>
  # Intel Classicの場合
  $<$<CXX_COMPILER_ID:Intel>: ...>
  )

ステップ5:依存ライブラリをリンクする必要があるとき

次に依存ライブラリをリンクすることを考えます。
例えば、mathライブラリをリンクするにはリンクオプションに-lmを指定します。

g++ -o <target> <ソースファイル> -lm <その他リンクオプション>

CMakeの場合はtarget_link_librariesを使ってターゲットに必要なライブラリをリンクさせればいいです。

target_link_libraries(<target> PUBLIC m)

ライブラリを直接指定せず、find_packageというものを使って名前やパスを取得し、target_link_librariesに渡してリンクすることもできます。
CMake: How to Find Libraries
CMake Documentation: find_package

find_packageで利用可能なライブラリ名はcmake --help-module-listで得ることができます。試しに調べたところ、科学技術計算に使いそうなものは一通りそろっているようでした。

  • FindLAPACK
  • FindPythonInterp
  • FindPythonLibs
  • FindMatlab
  • FindBLAS
  • FindArmadillo
  • FindBoost