こんにちは。正月太りで焦っている田原です。

さて、CMakeはadd_executableなどを使うことでビルド・システムを構築できますが、それだけではかゆいところに手が届きません。そこに手が届くようプログラミング言語的な機能も持っています。今回から数回でこの部分について解説します。まず今回は変数とそれを操作する基本的なコマンドについて解説します。

1.まずは基本から

CMakeの変数は文字列を記録します。数値として解釈できる文字列を取り扱うコマンド(math)も用意されています。
つまり、特に「型」はありません。文字列変数だけです。とはいえ、文字列だけにしては意外なほど複雑です。

1-1.設定と表示方法

まずは、例題を書けるよう変数を設定したり表示したりするコマンドについて説明します。

変数に値を設定するコマンドとしてsetコマンドがあります。(変数に値を設定するコマンドは他にも色々ありますが、一番基本的なsetを使います。)
構文は下記の通りです。(C++になれているとつい ;(セミコロン)を最後に付けたくなりますが、つけるとエラーになりますのでご用心。)

set(変数名 値)

また、変数の内容を確認するには message コマンドが便利です。messageコマンドは文字列を表示します。

message(文字列)

messageコマンドはエラーを発生させて処理を中断ような時にも使います。C++におけるabortやexit的な機能もmessageコマンドが担っています。ちょっとびっくりですね。この機能のため、messageコマンドの最初に書いた文字列は意味を持つ場合があります。例えば、WARNINGは警告を出力するという意味があります。

以前、警告を出力したくて、次のような感じで書いたことがありますが、かなり悩んでしまいました。

message(WARNING Be_carefull!!)

次のように出力されるのです。

CMake Warning at 01.cmake:1 (message):
  Be_carefull!

WARNINGではなくSEND_ERRORと書くと、エラーを出力し、そこで生成処理が中断されます。
SEND_FATALなら、生成処理とスクリプト実行の両方が中断されます。
詳しくは、本家のリファレンスをご確認下さい。

一度ハマって以来、私は単に表示するだけの時はいつも STATUS(情報出力)を付けて使っています。デバッグのために変数を出力する時がよくありますが、変数の内容によって振る舞いが変わってしまうのを避けたいですから。

message(STATUS test)
> cmake -P sample.cmake
-- test

1-2.変数

変数名にはアルファベット(大文字、小文字)、_(アンダーライン)、-(ハイフン)を使えます。
そして、${変数名}で変数の内容を取り出せます。基本はたったこれだけです。

set(VAR_NAME value)
message(STATUS ${VAR_NAME})
> cmake -P sample.cmake
-- value

2.クオーテーション

文字列と言えばダブル・クオーテーションで括ることが多いですね。CMakeもダブル・クオーテーションを使えますし、似たような場面で使います。しかし、C++のダブル・クオーテーションの考え方とはかなり異なっていて、shell(linux)やコマンド・プロンプト(Windows)の方に似ています。

setコマンドで設定する時、上述したようにダブル・クオーテーションで括る必要はありませんし、ダブル・クオーテーションで括ってもOKです。

set(VAR_NAME value)
message(STATUS ${VAR_NAME})
set(VAR_NAME "value")
message(STATUS ${VAR_NAME})
> cmake -P sample.cmake
-- value
-- value

そして、スペースを文字列に含めたい時には””で括っておけば、予想外の振る舞いに悩まないで済みます。

set(VAR_NAME "This is a message.")
message(STATUS ${VAR_NAME})
set(VAR_NAME This is a message.)
message(STATUS ${VAR_NAME})
> cmake -P sample.cmake
-- This is a message.
-- Thisisamessage.

ダブル・クオーテーションで括らない後者はせっかくスペースで単語の間を区切ったのにそれが無くなってしまいました。
これは単に区切りが無くなるという振る舞いではなく、リスト(複数の文字列)になったからです。

3.リスト

C++でも複数の文字列を扱うことができますね。文字列の配列型変数に記録することが多いと思います。
しかし、CMakeの変数は文字列を格納しますが、同じ変数で複数の文字列も記録できます。普通は文字列を1つだけ格納しますが、設定時にちょっと細工をするだけで文字列を複数格納できるのです。ダブル・クオーテーションで括らないでスペースで区切ることもその細工の1つです。

そして、リストを普通にmessageコマンドで出力すると、そのリストに含まれる文字列が区切り記号なしで出力されます。そのため、前節のmessageコマンドのようにスペースが無くなってしまうのです。

そしてもう一つの細工はダブル・クオーテーションで括った文字列の中を ;(セミコロン)で区切ることです。その区切りで文字列が区切られ、リストになります。

set(VAR_NAME This is a message.)
message(STATUS ${VAR_NAME})
set(VAR_NAME "This;is;a;message.")
message(STATUS ${VAR_NAME})
> cmake -P sample.cmake
-- Thisisamessage.
-- Thisisamessage.

4.ダブル・クオーテーション再び

C++に慣れていると衝撃を受けるのですが、’message(STATUS “${VAR_NAME}”)がどうなるかというと、問答無用で${VAR_NAME}`はVAR_NAME変数の内容に置き換わります。ちょっと意外ですね。

set(VAR_NAME "This is a message.")
message(STATUS ${VAR_NAME})
message(STATUS "${VAR_NAME}")
> cmake -P sample.cmake
-- This is a message.
-- This is a message.

更に、${${VAR_NAME}}のような記述も問答無用で展開されます。このような使い方はVariable Referencesと呼ばれています。

set(VAR_NAME FOO)
set(FOO "This is FOO.")
message(STATUS "${${VAR_NAME}}")
> cmake -P sample.cmake
-- This is FOO.

さて、先述したように ;(セミコロン)で区切られた文字列をダブル・クオーテーションで括るとリストになりました。
では、messageコマンドで出力する時にダブル・クオーテーションで括るとどうなるでしょう?

set(VAR_NAME This is a message.)
message(STATUS "${VAR_NAME}")
set(VAR_NAME "This;is;a;message.")
message(STATUS "${VAR_NAME}")
> cmake -P sample.cmake
-- This;is;a;message.
-- This;is;a;message.

あらら、スペースで区切った時でさえ、セミコロンが普通にでてきました。
どうやらCMake のリストの内部表現は、文字列を ;(セミコロン)で区切った「文字列」ということのようです。

5.エスケープ・シーケンス

以上にように、ダブル・クオーテーションやセミコロンは特殊記号です。このような特殊記号自体を文字列に含めたい時のためにエスケープ・シーケンスが用意されています。

CMakeのエスケープ・シーケンスは \(バックスラッシュ/円マーク)に続く1文字です。
3種類あります。

1.\に続く、英数字とセミコロン以外の一文字
2.\t、\r、\n
3. \;

1.は \に続く1文字になります。
2.は C++と同じです。それぞれタブ、CR、LFになります。
3.は、なかなか特殊です。ダブル・クオーテーションで括られている場合は \; のまま、括られていない場合は ; になります。(Variable Referencesの中も;になるそうです。)
複雑な仕様に見えますが、上述の強調したリストの内部表現と照らし合わせると理解できると思います。要するにリストにするための文字列区切りと混同しないように振る舞うのです。(文字列区切りが ; になる場面では \;。文字列を区切って無くなる場面では ;)

なお、\aなど上記にないものはエラーになります。

それぞれの代表的な振る舞いを可視化してみました。特に \; の振る舞いは複雑ですので注意深く御覧下さい。

set(VAR_NAME "A\tB\rC\nD\\E\"F")
message(STATUS "${VAR_NAME}")
set(VAR_NAME  A\tB\rC\nD\\E\"F)
message(STATUS "${VAR_NAME}")

message(STATUS "---------------")
set(VAR_NAME "A\;B")
message(STATUS "${VAR_NAME}")
message(STATUS  ${VAR_NAME})
set(VAR_NAME  A\;B)
message(STATUS "${VAR_NAME}")
message(STATUS  ${VAR_NAME})
C- A    B
D\E"F
C- A    B
D\E"F
-- ---------------
-- A\;B
-- A;B
-- A;B
-- AB

6.まとめ

今回はCMake変数の振る舞いについて纒めてみました。bashに慣れている人はそれ程戸惑わないと思いますが、CMakeを使い始めた当時、私はbashに慣れていなかったので、このCMake変数の「奇妙に見える振る舞い」にかなり戸惑いました。普通にシェル・スクリプトとして考えると「よくある」仕様なのですが、C++の文字列とは大きく異なるからです。

また、エスケープ・シーケンスについては翻訳はちらほら見かけるのですが、解説をあまり見かけません。私もよく混乱していましたのでこの機会に調べながら解説してみました。うまく解説できていることを祈りつつ、今回はこれにて終わります。お疲れ様でした。