LoginSignup
212
170

CMakeスクリプトを作成する際のガイドライン

Last updated at Posted at 2018-11-24

はじめに

CMakeを使い始めて半年以上経ちますが、まだまだ把握していない機能が多いです。
勉強のためYouTubeに上がっているCMakeに関する動画をいくつか見てきて、非推奨なコマンドや書き方があることがわかったので、それらをここにまとめておきます。

参考資料

ガイドライン

基本

現代的なCMakeの使い方は次の3点で表されます。

  1. ターゲット(target)を基本としたビルドシステム
  2. 依存関係を一箇所で指定する
  3. ターゲットがそれに依存するものへコンパイルおよびリンクに関する情報を提供する

すなわち、 target_で始まるコマンドを使用し、ターゲットに対する要件を指定する ことが基本となります。
CMakeのバージョンは2.8.12〜、実務上は3.0.0以降を指します。

現在は非推奨となっているコマンド

下記コマンドはターゲットに関わらず設定してしまうため使うべきではありません。

  • include_directories
  • add_definitions, add_compile_definitions, add_compile_options
  • link_directories
  • link_libraries

例えば、include_directoriesはコンパイラのファイルを探すパスに与えられたディレクトリを追加します。このコマンドを使用すると、定義したライン以降の全てのターゲットが指定したディレクトリをインクルードしてしまいます。include_directoriesの代わりにtarget_include_directoriesを使うようにしましょう。

非推奨
include_directories(${MYLIB_INCLUDE_DIRS})
add_library(mylib SHARED mylib.cpp)
#このライン以降もFOO_INCLUDE_DIRSは有効
推奨
add_library(mylib SHARED mylib.cpp)
target_include_directories(mylib
  PUBLIC ${MYLIB_INCLUDE_DIRS}
)

ただし、他のターゲットに属するディレクトリに対しては、target_include_directoriesを用いるべきではありません。すなわち

非推奨
add_library(mylib SHARED ${MYLIB_SRC_FILES})

add_executable(myapp myapp.cpp)
target_link_libraries(myapp PRIVATE mylib)
target_include_directories(myapp PRIVATE ${MYLIB_INCLUDE_DIRS})
推奨
add_library(mylib SHARED ${MYLIB_SRC_FILES})
target_include_directories(mylib PUBLIC ${MYLIB_INCLUDE_DIRS})

add_executable(myapp myapp.cpp)
target_link_libraries(myapp PRIVATE mylib)
# MYLIB_INCLUDE_DIRSをmylibのインクルードディレクトリとしてPUBLICスコープで指定すれば、
# 自動的にmylibに依存するmyappのインクルードディレクトリとなる。

CMAKE_<LANG>_FLAGSにオプションを足さない

CMAKE_<LANG>_FLAGSの代わりにtarget_compile_optionsを使いましょう。

非推奨
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
推奨
target_compile_options(mylib PUBLIC -Wall)

CMAKE_CXX_FLAGStarget_compile_options-std=c++11/c++14/c++17/c++20を加えない

CMAKE_CXX_STANDARD(CMake 3.1以降)を使うか、target_compile_featurescxx_std_11/cxx_std_14/cxx_std_17/cxx_std_20を加えましょう(CMake 3.8以降)。

非推奨
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
推奨
target_compile_features(mylib PUBLIC cxx_std_17)

変数を使い過ぎない

setを使用して定義される変数には以下のような問題点があります。

  • つづりを間違えやすい
  • 他の文脈へリークしやすい(定義した以降のあらゆる場所で使用可能)
  • 変数が依存するもののスコープが不明
  • 変数の内容やその正しさがチェックされない

変数を使う必要がなくてもかける場合は使わないようにしましょう。
特にファイルをターゲットに追加する場合、変数の代わりにtarget_sourcesを使いましょう。

非推奨
set(MYLIB_SRCS
  file1.cpp
  file2.cpp
  )
add_library(mylib ${MYLIB_SRCS})
推奨
add_library(mylib
  PRIVATE
    file1.cpp
    file2.cpp
  )

PRIVATE/PUBLIC/INTERFACEを適切に使う

これらのキーワードは、コマンドのターゲットおよびそのターゲットに依存するターゲットに対する必要性を表します。

キーワード ターゲットが必要とする ターゲットに依存するターゲットが必要とする
PRIVATE :o: :x:
PUBLIC :o: :o:
INTERFACE :x: :o:

ヘッダーのみのライブラリにはINTERFACEを使います。

if文よりもGenerator Expressionを使う

CMakeは

  1. Configure - CMakeLists.txtをパースしキャッシュ変数をCMakeCache.txtに書き込む
  2. Compute - 依存関係を計算する
  3. Generate - ビルドファイル(Makefile等)を作成する

の三段階に分けて実行されます。
この中で、CMakeのif文(if()/elseif()/else()/endif())はConfigure時に実行されるのに対し、Generator Expression($<...>)はGenerateの直前に評価されるという違いがあります。この評価時期の違いによりCMakeが想定と異なる動作を引き起こすことがあります。

したがって、Configureをした後に評価するもの:

  • CONFIG(Release/Debug/...)
  • TARGET_PROPERTY
  • TARGET_POLICY
  • COMPILE_FEATURES
  • LOCATION

に対してはGenerator Expressionsを使うようにします。

例えばVisual C++の場合、CMakeLists.txtをConfigureするのはCMakeLists.txtを含むフォルダを開いた時のみであり、ビルドタイプ(Release/Debug等)はデフォルトでDebugとして評価されます:

When you choose File | Open | Folder to open a folder containing a CMakeLists.txt file, the following things happen:
(中略)

  • Visual Studio runs CMake.exe and generates the CMake cache for the default configuration, which is x86 Debug. ...

CMake Projects in Visual C++: IDE Integration

したがって、その後にIDE上でビルドタイプをReleaseに変更しても既にキャッシュ変数として保存されたCMAKE_BUILD_TYPEは変わらないため、CMakeスクリプト内でCMAKE_BUILD_TYPEを使って評価する部分はDebugのまま評価されてしまうということが起きます。if文でなくGenerator Expressionを使い、CMAKE_BUILD_TYPEを使わないようにすると、この問題を避けることができます。

ビルドタイプ(Release/Debug)に応じてファイルを変更したい場合、以下の2つは同じことを意図していますが、if文を使っている方はVisual C++で使うとビルドタイプを変えた時に適切に反映されません。

if文
set(MYAPP_SRCS
  main.cpp
)
if (CMAKE_BUILD_TYPE STREQUAL DEBUG)
  list(APPEND MYAPP_SRCS logger_debug.cpp)
else()
  list(APPEND MYAPP_SRCS logger_release.cpp)
endif()

add_executable(myapp ${MYAPP_SRCS})
GeneratorExpression
add_executable(myapp
  main.cpp
  $<IF:$<CONFIG:Debug>,logger_debug.cpp,logger_release.cpp>
)

file(GLOB)を使わない

file(GLOB)は、CMakeを実行するたびに条件に合致するファイルのリストを自動的に作成する非常に便利なコマンドです。ところがこのコマンドはIDEで使う場合うまく動作しない場合があります(Visual C++等)。したがって、常にcmakeをコマンドラインから実行するような場合でない限り使うべきではありません。
ディレクトリごとにCMakeLists.txtを作成し、add_subdirectorytarget_sourcesを使って再帰的にファイルを明示して追加しましょう。

非推奨
# /src/CMakeLists.txt
# 子ディレクトリ内のソースファイルを再帰的に探索し変数へ追加
file(GLOB_RECURSE MYLIB_SRCS "*.cpp")
add_library(mylib ${MYLIB_SRCS})
推奨
# /src/CMakeLists.txt
add_library(mylib)
add_subdirectory(dir1)
add_subdirectory(dir2)
# ... 子ディレクトリを同様に追加していく

# /src/dir1/CMakeLists.txt
# CMake-3.12まで
target_sources(mylib
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/file1.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/file2.cpp
    # 絶対パスで指定しないとエラーになるので注意
  )
# CMake-3.13以降
cmake_policy(SET CMP0076 NEW) # CMakeが自動的に相対パスを絶対パスへ変換する
target_sources(mylib
  PRIVATE
    file1.cpp
    file2.cpp
  )

マクロの代わりにfunctionを使う

マクロは呼び出す側のスコープにある変数を上書きしてしまいます。自身のスコープを持つfunctionを使いましょう。親ディレクトリのスコープにある変数を書き換える場合はset(<variable> <value>... PARENT_SCOPE)を使いましょう。

キャッシュ変数には必ず接頭辞(prefix)を付ける

キャッシュ変数はグローバル変数です。名前の衝突を避けるために接頭辞をつけましょう。

CMAKE_SOURCE_DIRを使わない

代わりにCMAKE_CURRENT_SOURCE_DIRPROJECT_SOURCE_DIR<PROJECT-NAME>_SOURCE_DIRを使いましょう。

CMAKE_SOURCE_DIRはトップレベルのディレクトリを指します。異なるプロジェクトがネストしている場合、自身のプロジェクトのルートディレクトリ以外のパスを示すので使わないようにしましょう。

必要ない限りライブラリの種類を指定しない

ビルドするユーザーが静的・共有ライブラリを決められるようにしましょう。
グローバルオプションBUILD_SHARED_LIBSを使って選択することもできますが、各ライブラリごとにオプションを設定しておく方がいいです。

option(MYLIB_BUILD_SHARED_LIBS "build mylib as a shared library" ON)

if (BUILD_SHARED_LIBS AND MYLIB_BUILD_SHARED_LIBS)
  add_library(mylib SHARED <files>...)
else()
  add_library(mylib STATIC <files>...)
endif()
212
170
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
212
170