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

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

はじめに

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_executable(myapp myapp.cpp)
target_link_libraries(myapp
  PRIVATE mylib
)
target_include_directories(myapp
  PRIVATE ${MYLIB_INCLUDE_DIRS}
)
推奨
add_executable(myapp myapp.cpp)
target_link_libraries(myapp
  PRIVATE mylib
)
# MYLIB_INCLUDE_DIRSはターゲット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を加えない

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

変数を使い過ぎない

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

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

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

非推奨
set(MYLIB_SRCS
  file1.cpp
  file2.cpp
  )
add_library(mylib ${MYLIB_SRCS})
推奨
add_library(mylib)
target_sources(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 (MYLIB_BUILD_SHARED_LIBS)
  add_library(mylib SHARED)
else()
  add_library(mylib STATIC)
endif()
shohirose
2015年8月からテキサス大学オースティン校の石油工学科博士課程に留学し、2019年12月にPhDを取得しました。現在は東京に住んで東京本社で働いています。専門は貯留層工学、フラクチャー力学、数値計算あたりです。プログラミング言語はc++, matlab, pythonを主に使用しています。
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
ユーザーは見つかりませんでした