CMakeを使ってビルドする(2)

2022-03-08C++,CMake

前回の記事で作成したCMakeLists.txtには、$<...>で囲まれた式(Generator-Expressions)が唐突に出てきました。
今回は、このGenerator-Expressionsについて少し細かく書いてみたいと思います。

前回の記事はこちら。

サブディレクトリを含む場合のCMakeLists.txtの書き方についてはこちら。

Generator-Expressionsとは

Generator-Expressionsはビルドシステムの設定ファイルの生成中に評価される式で、コンパイルオプションなど、ビルド構成に固有の情報を生成するために使われるものです。

CMakeではGenerator-Expressionsの使用が推奨されています。

Generator-Expressionの書式

大きく分けて値を返すものと、値を評価して0か1を返すものの2種類があります。使えるGenerator-Expressionsの一覧と詳細は公式ドキュメントを参照してください。

値を返すもの

# (1) 値そのものを返す
$<変数名またはキーワード>

# (2) 0または1を取る変数または式Aがあり、Aが1の時にのみ値Bを返す
$<変数A:値B>
$<式A:値B>

# (3) キーワードに従って値を処理する
$<キーワード:処理される変数名[,...]>

値を評価して0または1を返すもの

# (4) 変数名またはキーワードが指定された値に等しいか評価する
$<変数名またはキーワード:評価する値>

# (5) 論理演算
$<キーワード:式1 [式2 ...]>

# (6) 値を0または1に変換する
$<BOOL:値>

# (7) 値同士の比較
$<キーワード:比較対象1,比較対象2>

よく使うと思われるGenerator-Expressions

  • $<式:値>
    • 式(Generator-Expressions)が1の時のみ値を返す。
    • $<$<CXX_COMPILER_ID:GNU>: -Wall -Wextra>の外側の$<...>のこと。
  • $<IF:式,式が1の時の値,式が0の時の値>
    • 条件分岐。
    • $<IF:$<CONFIG:Debug>,DEBUG,NDEBUG>みたいに使う。
  • $<REMOVE_DUPULICATES:リスト>
    • リスト中の重複した値を1つを残してすべて取り除いたリストを返してくれる。
  • $<GENEX_EVAL:文字列>
    • 文字列をGenerator-Expressionsとして評価する。
    • 変数GENEXSTRにGenerator-Expressionsが文字列として代入されているとして、$<GENEX_EVAL:${GENEXSTR}>のように使用する。
  • $<CONFIG:ビルドタイプ>
    • CMAKE_BUILD_TYPEの値が指定したものか評価する。
  • $<CXX_COMPILER_ID:コンパイラ識別子[, ...]>
    • コンパイラの種類が指定したものか評価する。カンマで複数指定した場合は、そのうちのどれかに一致すれば1を返す。
    • GCCの場合はGNU、VC++の場合はMSVC、LLVM/Clangの場合はClangとする。何が使えるかはこちらを参照。
  • $<NOT:式>
    • 式の値を反転する。0なら1に、1なら0にする。
    • $<NOT:$<CXX_COMPILER_ID:MSVC>>とすると、VC++以外のコンパイラの時に1を返す。
  • $<BOOL:値>
    • 値が次の時0を返す。
    • 空文字列
    • 0, FALSE, OFF, NO, N, IGNORE, NOTFOUND(大文字小文字は問わない)
    • 文字列の末尾に-NOTFOUNDが付加されたもの(大文字小文字を区別する)
  • $<STREQUAL:値1,値2>
    • 2つの文字列を比較する。大文字小文字は区別される。
  • $<IN_LIST:値,リスト>
    • リスト中に値が含まれていれば1を返す。大文字小文字は区別される。

条件分岐との違い

Generator-Expressionsと似たもので、if()で始まる条件分岐も存在します。

if(式1)
  ...
elseif(式2)
  ...
else()
  ...
endif()

これも条件にしたがって動作を変えるために使われるものですが、評価のタイミングがGenerator-Expressionsとは異なるので注意が必要です。

Visual StudioやXcodeのように1つの設定ファイルに複数のビルドタイプの設定が保存されるものでは、実際のビルド時までどのビルドタイプを使うのか分かりません。このようなシステムに対しては、if()による分岐では特定のビルドタイプの設定しか設定ファイルに反映できません。このような場合には、Generator-Expressionsを使用します。

(参考)

  1. CMake公式ドキュメント – cmake-buildsystem(7)
  2. CMakeスクリプトを作成する際のガイドライン – Qiita

なお、条件分岐を使わざるを得ない場合もあります。たとえば、コンパイルオプションの初期値自体を書き換える場合です。VC++ではCMAKE_CXX_FLAGSで警告オプションの初期値が/W3となっているので、それを/W4に変更したい時、以下のようにすることで書き換えられます。

if(MSVC)
  string(REGEX REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()

Generator-Expressionsの注意点

Generator-Expressionsは、すべてのコマンドで使えるとは限らない点に注意が必要です。たとえば、set()ではGenerator-Expressionsは単なる文字列のリストとして扱われてしまいます。

コマンド内でGenerator-Expressionsが使用可能かどうかは、公式ドキュメントを参照してください。

Generator-Expressionsのデバッグ

Generator-Expressionsは式が評価されるタイミングの関係上、message()を使って結果を確認することができません。思った通りの動作をしない時の確認が難しいです。デバッグのためには、公式ドキュメントにあるようにファイルに出力するのが良いと思います。
(もうちょっと何とかしてほしいと思いますが・・・)

file(GENERATE OUTPUT filename CONTENT "$<...>")

まとめ

CMakeのGenerator-Expressionsについてまとめてみました。式の書き方にクセがあって少々わかりにくいのが難点ですが、設定や環境によってコンパイルオプション等を簡単に切り替えられるようになるので、便利な機能です。

C++,CMake

Posted by izadori