こんにちは。田原です。花粉症に悩まされる今日この頃です。

さて、今回も前回に引き続きCMakeの変数について解説してみます。CMake変数は文字列ですが、数値や配列(リスト)として取り扱うこともだきます。数値の場合は明示的に処理するので混乱しませんが、CMake変数は暗黙的に配列なので混乱しがちなのです。

1.数値としての取扱

CMake変数を数値として大小比較したり演算したりすることができます。

1-1.if文で数値として使う

CMakeにもif文があり、C++と同様に比較演算子があります。その比較演算子により、文字列として比較するのか数値として比較するのかを指定します。
==に相当するものがSTREQUALとEQUALの2種類です。前者は文字列として一致判定し、後者は数値として一致判定します。

次のサンプルでは、変数ONEには文字列 “1”を、変数ONE_FLOATには文字列 “1.0”を設定して両者が一致するのか判定しています。「文字列」として比較すると当たり前ですが、異なる文字列ですので不一致となります。数値として比較すると 1 と 1.0 は同じ値ですから一致となります。

set(ONE "1")
set(ONE_FLOAT "1.0")

if ("${ONE}" STREQUAL "${ONE_FLOAT}")
    message(STATUS "YES")
else()
    message(STATUS "NO")
endif()

if ("${ONE}" EQUAL "${ONE_FLOAT}")
    message(STATUS "YES")
else()
    message(STATUS "NO")
endif()
> cmake -P sample.cmake
-- NO
-- YES

数値としては 大なり(GREATER;>のこと)や、小なり(LESS;<)、以上(GREATER_EQUAL;>=)、以下(LESS_EQUAL;<=)等の比較も可能です。
文字列としては同様な比較を辞書順で行うことができます。それぞれ、STRGREATER、STRLESS、STRGREATER_EQUAL、STRLESS_EQUALを用います。

set(ONE "1")
set(ONE_FLOAT "1.0")

if ("${ONE}" LESS "${ONE_FLOAT}")
    message(STATUS "YES")
else()
    message(STATUS "NO")
endif()

if ("${ONE}" STRLESS "${ONE_FLOAT}")
    message(STATUS "YES")
else()
    message(STATUS "NO")
endif()
> cmake -P sample.cmake
-- NO
-- YES

if文はもっと様々な比較ができますが、全てを解説していると混乱しやすいので当ブログでは解説は見送る予定です。
詳しくは公式のリファレンスQiitaのアドベント・カレンダーを参考にされて下さい。

1-2.math文

CMakeでも簡単な数値計算ができます。四則演算に毛が生えた程度ですが、CMakeで連立方程式の解を数値計算で求めることはないでしょうから十分と思います。

構文は次の通りです。

math(EXPR 出力先変数名 数式)

現在(3.13.4)のところEXPRは単なるおまじないです。必ずかかないとエラーになります。他の記述は許されません。小文字さえダメでした。

数式に記述できる演算子は、+, -, *, /, %, |, &, ^, ~, <<, >> と ()括弧です。機能はC言語の演算子と同じです。
使える数値は整数だけのようです。1.0等はsyntax errorとなります。

set(ONE "1")
math(EXPR FOO "3 + 2*4 + ${ONE}")
message(STATUS "FOO=${FOO}")
> cmake -P sample.cmake
-- FOO=12

注意点としては、数式は1つの文字列しかかけないことです。後述しますが、スペースで区切られた文字列は複数の文字列(配列)となります。

set(ONE "1")
math(EXPR FOO 3 + 2*4 + ${ONE})
message(STATUS "FOO=${FOO}")
> cmake -P sample.cmake
CMake Error at sample.cmake:2 (math):
  math EXPR called with incorrect arguments.

次のようにスペースで区切らなければエラーにならないのですが、C++に慣れているとついスペースを使うこともあると思いますので、最初のサンプルのように数式は””(ダブルコーテーション)で括ることをお勧めします。

set(ONE "1")
math(EXPR FOO 3+2*4+${ONE})
message(STATUS "FOO=${FOO}")
-- FOO=12

ところで、mathコマンドは 3.13で機能アップされているようです。結果を16進数文字列へ変換する機能が追加されています。

2.CMakeの文字列は ;(セミコロン)区切りの配列(リスト)でもあります

CMakeでは前述のように文字列を数値として解釈して取り扱うことができます。C++のように文字列と数値が厳密に異なっているというものではないことにご注意下さい。
そして、CMakeは、文字列を区切り記号 ;(セミコロン)で区切ると配列(リスト)として取り扱います。この点でも考え方がC++と大きく異なっていますのでC++erにとっては慣れが必要です。

2-1.CMake配列の基本的な振る舞い

下記はsetコマンドで変数BARとBAZへ、abc, def, ghiという文字列3つを配列として設定します。””で括って表示すると ;(セミコロン)で区切られていることが分かります。

set(BAR "abc" "def" "ghi")
message(STATUS "BAR=${BAR}")
set(BAZ "abc;def;ghi")
message(STATUS "BAZ=${BAZ}")
-- BAR=abc;def;ghi
-- BAZ=abc;def;ghi

ところで、””で括らないとどうなるでしょう?
ちょっとその前にmessageの振る舞いの再確認です。

message(STATUS "abc" "def" "ght")
-- abcdefght

CMakeのmessageコマンドは、複数の文字列が与えられたらスペース等で区切ることなく続けて出力するようです。

set(BAR "abc" "def" "ghi")
message(STATUS "BAR=${BAR}")
set(BAZ "abc;def;ghi")
message(STATUS "BAZ=${BAZ}")
-- BAR=abcdefghi
-- BAZ=abcdefghi

つまり、CMakeの文字列は、次のような性質を持っています。
1. 複数の文字列を” “(ブランク)で区切ってsetすると配列として結合されます。
2. ;(セミコロン)で区切られた文字列は配列(複数の文字列)として取り扱われます。
3. 更に、;(セミコロン)区切りの文字列を””で括ってコマンドに渡すと、そのまま1つの文字列として取り扱われます。

なお、もし;(セミコロン)がなければ、区切りがないので1つの文字列ですね。これは要素数が1つの配列と考えても良さそうです。

このように、CMakeは配列と非配列は基本的に同じものであり、相違は区切り記号で区切られているか否かということになります。
;(セミコロン)自体を含む文字列を定義したい時は、エスケープします。

2-2.配列(リスト)を取り扱うコマンド

配列の要素数を取り出したり、配列のインデックスを指定して操作する list コマンド、および、C++の範囲ベースforのような foreach をよく使います。

2-2-1.listコマンド

ざっくり以下のように使います。

set(BAR "abc" "def" "ghi")
list(LENGTH BAR LEN)      # BARの要素数を変数LENへ取り出す
message(STATUS "LEN=${LEN}")
list(GET BAR 2 VAL)       # BARのインデックス=2の要素を変数VALへ取り出す
message(STATUS "VAL=${VAL}")
-- LEN=3
-- VAL=ghi

他にも色々な機能があります。詳しくは公式のリファレンスQiitaのアドベント・カレンダーを参考にされて下さい。

2-2-2.foreachコマンド

ざっくり以下のように使います。

set(BAR "abc" "def" "ghi")
foreach(ITEM IN LISTS BAR)    # 配列BARの内容を先頭から順に変数ITEMへ取り出す
    message(STATUS "ITEM=${ITEM}")
endforeach()
foreach(I RANGE 0 2)          # 変数Iの値を順に0, 1, 2へ設定する
    list(GET BAR ${I} ITEM)   # 配列BARのインデックスがIの要素を変数ITEMへ取り出す
    message(STATUS "BAR[${I}]=${ITEM}")
endforeach()
-- ITEM=abc
-- ITEM=def
-- ITEM=ghi
-- BAR[0]=abc
-- BAR[1]=def
-- BAR[2]=ghi

foreachコマンドもlistコマンドと同様に、も色々な機能があります。詳しくは公式のリファレンスQiitaのアドベント・カレンダーを参考にされて下さい。

3.まとめ

CMakeで数値を取り扱うことは稀ですが、たまには使いたいケースがあり、CMakeはそれに対応しています。
そのような時、特にif文での判定は、数値、文字列どちらで比較しても同じ結果になる場合が多いのでハマりやすいです。サンプルで示したものや プラスやマイナスの記号が頭についている場合など必ずしも同じ結果にはならないので要注意です。

配列といいますかリストは結構使います。おいおい解説予定のfind_packageコマンドで返ってくるライブラリはリンクするファイルが複数あることが多いため、リストとして返ってくるケースが多いです。意外によく使いますので要注意です。

そして、前回と今回で解説したようにCMakeは文字列だけを使って様々な機能を実装しています。C++では様々な型を用意してそれぞれで対応しているものを文字列だけに集約しているので把握するのに苦労します。といいますか、私は結構苦労しました。みなさんも頑張って下さいね。
それはで今回はこれにて失礼します。お疲れ様でした。