かみのメモ

コンピュータビジョン・プログラムな話題中心の勉強メモ

勝手に作るCMake入門 その3 プロジェクトの設定

勝手に作るCMake入門の3本目です。

1本目の記事ではCMakeのhello_worldプロジェクトを作成し、2本目の記事では静的ライブラリを利用してプロジェクトを階層化しました。

今回はCMakeLists.txtなどのCMakeスクリプトの文法を改めて確認していきます。 併せてキャッシュ変数の役割ccmakeやcmake-guiを使ったプロジェクト設定について紹介します。 そしてプロジェクト設定の一例としてDebug, Releaseなどのビルドタイプを切り替える方法についても紹介します。

全体のもくじ

  1. 基本的な使い方
  2. プロジェクトの階層化
  3. プロジェクトの設定【今ここ】
  4. 外部ライブラリを利用する

この記事のもくじ

スポンサーリンク

5. CMakeプロジェクトにおける設定

5.1. 検証用のCMakeスクリプト

まずは手を動かす意味でCMakeスクリプトを書いてみましょう。

適当なプロジェクトディレクトリを作成し、そこにCMakeLists.txtを作成します。

cmake_minimum_required(VERSION 3.1)
set(MY_VARIABLE_A hogehoge)
set(MY_VARIABLE_B ${MY_VARIABLE_A} CACHE STRING "Test for cache variable")
message(STATUS "MY_VARIABLE_A : ${MY_VARIABLE_A}")
message(STATUS "MY_VARIABLE_B : ${MY_VARIABLE_B}")
if("${MY_VARIABLE_B}" MATCHES "^fuga")
  message("MY_VARIABLE_B starts with 'fuga'")
endif()

各行でやっていることを順に確認してみましょう。

1行目 CMakeのバージョン指定

これまでと同じようにCMakeのバージョンを指定しています。

2行目 普通の変数の宣言

2〜3行目ではMY_VARIABLE_A, MY_VARIABLE_Bという2つの変数を宣言しています。 CMakeの変数には普通の変数(normal variable)とキャッシュ変数(cache variable)の2種類があります。 2行目で宣言しているMY_VARIABLE_A普通の変数の方です。

普通の変数は他のプログラミング言語の変数と同じく、処理を書くときに一時的に値を格納しておくために利用します。 スクリプトの中で一時的に使われるだけなので、プロジェクトをConfigure, Generateして使うユーザの目には触れることのない変数です。

CMakeの変数名は大文字と小文字を区別します。 よく使われるのはVARIABLE_NAMEのような全て大文字のスネークケースですので、これに合わせて命名するのが無難です。

変数を宣言するときや値をセットするときはset()コマンドを呼びます。 ちなみに、あまり使いませんが変数を取り消すunset()コマンドもあります。 普通の変数は2行目のようにset(MY_VARIABLE_A hogehoge)の構文で宣言します。 ここでは変数MY_VARIABLE_Ahogehogeという値を代入しています。 CMakeの値は全て文字列もしくは文字列のリストとして扱われます。

変数をうまく利用すれば、リストに対してforeach()でループを回したり比較演算子や論理演算子を使って条件分岐させたりすることもできます。

3行目 キャッシュ変数の宣言・値の展開

3行目ではMY_VARIABLE_Bというキャッシュ変数を宣言し、そこに先ほど宣言した変数MY_VARIABLE_Aの中身を代入しています。

キャッシュ変数はset(<variable name> <value> CACHE <type> <description>)の構文で宣言します。 指定できるtypeにはBOOL, PATH, FILEPATH, STRING, INTERNAL, UNINITIALIZEDがあります。

またON/OFFを扱うキャッシュ変数だけは専用のコマンドoption(<variable name> <description> [initial value])で宣言します。 set(... BOOL ...)でも似たような変数を宣言できますが、option(...)で宣言したキャッシュ変数の方が、初期値があったり複数のオプション同士を連動させることができたりと多機能です。 詳しいことは他の記事を参照してください。

キャッシュ変数はその名の通り、Configure後に値がキャッシュされる変数です。 一度プロジェクトのConfigureを実行すると、キャッシュ変数はCMakeCache.txtというファイルの中に保存され次回以降Configureするときにも引き継がれます。 キャッシュ変数は2回目以降のConfigurationの時間を短縮するために使われる他、Configure時にユーザーがプロジェクトの設定を変更できるようにするために利用されます。

typeとしてINTERNALを指定すると、後で説明するccmakeやcmake-guiに表示されなくなりユーザーからは見えないキャッシュ変数として利用できるようになります。 INTERNALなキャッシュ変数は主に2回目以降のConfigurationの時間を短縮するために利用されます(いわゆる普通のキャッシュとしての役割)。 例えば、実行時間が長いCMakeスクリプトから得た情報をキャッシュしておけば次回以降Configureし直すときに実行時間を短縮できます。 こうした目的で使うキャッシュ変数はユーザーに見せる必要がないのでINTERNALとして作成します。

INTERNAL以外を指定するとccmakeやcmake-guiから閲覧・編集できるようになります。 このタイプのキャッシュ変数はConfigure時にユーザーがプロジェクトの設定を変更できるようにするために利用されます。 この辺りのやり方は次の項で解説します。

最後にCMake変数の値の展開についてです。 CMake変数の値を展開するときは${MY_VARIABLE_A}のように${}で囲います。 よって、3行目のset(MY_VARIABLE_B ${MY_VARIABLE_A} ...)set(MY_VARIABLE_B hogehoge ...)と書いたのと同じように解釈されます。 存在しない変数を展開しようとしたときはnull扱い、つまり空文字でさえなく、そこに何も書かなかったのと同じ扱いになります。 コマンドによっては引数が足りないとエラーを出すことがあるので気をつけましょう。

4〜5行目 メッセージ(ログ)の表示

4〜5行目ではmessage()コマンドを利用して、ユーザーへのメッセージとして変数の中身を表示しています。

message()コマンドにはいくつかのモードがあるので状況に応じて使い分けましょう。 以下にいくつか例を示します。

message([[
This is an example of
multiline message.]])

message(STATUS "Hoge Fuga Piyo") # 重要でないメッセージはSTATUSモードで

message(FATAL_ERROR "ERROR!!!")  # メッセージと共にConfigurationをエラー終了させる

6〜8行目 ifコマンドによる条件分岐

6〜8行目ではif()コマンドを使って条件分岐させ、変数MY_VARIABLE_Bの値がfugaから始まるものであるときだけメッセージを表示するようにしています。

ここではMATCHES正規表現による一致判定を行っていますが、他にもLESS, EQUAL, ANDなどを使った条件文を書くことができます。


以上が今回使用するCMakeLists.txtです。 今回はCMakeスクリプトの挙動を見たいだけなのでソースコードは用意しません。

作成したCMakeLists.txtを実行してみましょう。 いつもどおりbuildディレクトリを作成し、その中でConfigureを実行します。

mkdir build
cd build
cmake ..

以下、実行結果です。

(省略)
-- MY_VARIABLE_A : hogehoge
-- MY_VARIABLE_B : hogehoge
-- Configuring done
-- Generating done
(省略)

ちゃんとMY_VARIABLE_AとMY_VARIABLE_Bの中身がhogehogeになっているというメッセージが表示されました。

5.2. キャッシュ変数を利用したプロジェクトの設定

次にキャッシュ変数を利用してCMakeプロジェクトに設定項目を設ける方法を紹介していきます。

ここで言うプロジェクトの設定とは、例えばGPUサポートのON/OFF, Python Bindingを作成するかのON/OFF, ビルドに必要な外部パッケージへのパスなどのことです。 GPUサポートやPython Bindingが必要かどうかは時々によって切り替えたいものですし、外部パッケージがインストールされているパスは開発環境によって違う可能性があるため、ユーザーが指定できるようにしておく必要があります。

CMakeではキャッシュ変数の仕組みを利用して、

  1. 開発時にCMakeLists.txtにキャッシュ変数を作成・利用するコードを書いておく
  2. Configurationを実行する(キャッシュ変数にはデフォルト値が設定される)
  3. ユーザーが必要に応じてキャッシュ変数を上書きする
  4. もう一度Configuration, Generationを実行する(最終的にキャッシュされていた値が使われる)

という形でこれらの設定を反映させます。

一度作成されたキャッシュ変数は以下のいずれかで上書きされます。

  1. -Dオプションを付けてConfigureする
    • cmake .. -D<variable name>=<value>
  2. ccmakeやcmake-guiで編集する(後述)
  3. Configure中にFORCE付きのsetコマンドを実行する
    • INTERNALで作成されたキャッシュ変数のみ、FORCEなしのsetコマンドでも上書きできる

とまあ、解説ばかりでもあれなので手を動かしながら確認していきましょう。

5.3. コマンドラインからキャッシュ変数を指定する

CMakeではConfigure時に-Dオプションを付けることでキャッシュ変数の値を上書きすることができます。

先ほど作成したCMakeLists.txtを以下のコマンドでConfigureし直してみましょう。

cmake .. -DMY_VARIABLE_B="fugafuga"

CMakeではオプションの後のスペースを省略できるので以下の2つは同じ意味を持ちます。

  • -DMY_VARIABLE_B="fugafuga"
  • -D MY_VARIABLE_B="fugafuga"

キャッシュ変数の数が多くなるとコマンドがどんどん長くなるので、スペースは省略することが多いです。

さて、このコマンドでConfigureを実行すると実行結果が以下のように変わっているはずです。

-- MY_VARIABLE_A : hogehoge
-- MY_VARIABLE_B : fugafuga
MY_VARIABLE_B contains 'fuga'
-- Configuring done
-- Generating done

キャッシュ変数の中身がhogehogeからfugafugaに上書きされ、if文の分岐によりMY_VARIABLE_B contains 'fuga'が表示されています。 ここでもう一度、オプションなしでConfigureを実行してみましょう。

cmake ..

上書きされたキャッシュ変数の値はCMakeCache.txtの中に保存されているので、再びオプションなしでConfigureを実行しても値はfugafugaのままです。 Configureの際にset(MY_VARIABLE_B ${MY_VARIABLE_A} ...)が呼び出されはするものの、このコマンドは作成済みのキャッシュ変数は上書きしません(もちろんCMakeCache.txtを削除してから再度Configureするとhogehogeに戻ります)。

つまりこの状態でGenerateを行えばMY_VARIABLE_BにfugafugaがセットされたものとしてCMakeスクリプトが実行され、プロジェクトファイルが作成されます。 キャッシュ変数の値をうまく利用するようなCMakeLists.txtを書いておけば、ユーザーが後からキャッシュ変数を通してプロジェクトの設定を調整できるようになるというわけです。

5.4. ccmakeとcmake-guiの使い方

さて、先ほどコマンドラインからキャッシュ変数を-Dオプションで上書きする方法を紹介しました。 しかしこの方法には2つの欠点があります。

  1. そのCMakeプロジェクトで利用できる変数名とその役割がわからない
  2. 今キャッシュされている値を知ることができない

一応cmake .. -LHを実行することで変数名, description, 現在の値の一覧を見ることはできます。

(省略)

// Test for cache variable
MY_VARIABLE_B:STRING=fugafuga

ただ、もう少し便利にキャッシュ変数の値を操作できるようにしたいところです。

そのために用意されているのがccmake, cmake-guiというツールです。 WindowsではCMakeをインストールしたときにcmake-guiもインストールされているはずなのでこれを使います。 *nix系のOSではどちらも使うことができますが、OSパッケージマネージャーでCMakeをインストールしたときに付いてくるのはccmakeの方であることが多いのでこちらを使います。 どちらも機能面での違いはありません。

ccmakeを起動するときは、buildディレクトリの中で以下のコマンドを実行しましょう。

ccmake ..

もし明示的にGeneratorを指定するならこのタイミングで-G"Unix Makefiles"のように指定します。

f:id:kamino-dev:20190609181459p:plain:w400
ccmakeを起動した様子

ccmakeの操作方法は画面下部に表示されているとおりです。 矢印キーもしくはj, kでカーソルが上下に移動すると、中段に選択中の変数のdescriptionが表示されます。 Enterでその項目を編集し、cでConfigureを実行、gでGenerateを実行してccmakeを終了します。

cmake-guiを起動するときは、buildディレクトリの中で以下のコマンドを実行しましょう。

cmake-gui ..

f:id:kamino-dev:20190609181411p:plain:w400
cmake-guiを起動した様子

使い方はccmakeと同じで、Configureを実行し、キャッシュ変数を編集し、Generateを実行してcmake-guiを終了させます。

advanced cache entry

ちなみに、普段触らないディープな設定に関わるキャッシュ変数はmark_as_advanced(<variable name>)によってadvancedに設定することができます。 advancedに設定されたキャッシュ変数は、ccmakeやcmake-guiでshow advancedにチェックを入れない限りユーザーに表示されないようになります。 コマンドラインでadvancedなキャッシュ変数も含めて変数の一覧を表示させたいときはcmake .. -LAを実行しましょう。

CMakeがデフォルトで作成するキャッシュ変数

ccmakeやcmake-guiでキャッシュ変数の一覧を見ると、CMAKE_INSTALL_PREFIXなど宣言した覚えのないものがいくつか表示されていると思います。 これらはCMakeがデフォルトで作成するキャッシュ変数です。 コンパイラのパスやコンパイルオプションなどもキャッシュ変数に格納されているので、これを上書きすることで設定を変更できます。 これらの変数の役割や使い方については公式ドキュメントを参照してください。

6. ビルドタイプの設定

最後に変数を通したプロジェクト設定の例として、Debug, Releaseなどのビルドタイプを指定する方法について紹介します。

この設定にはCMAKE_BUILD_TYPE, CMAKE_CONFIGURATION_TYPESという2つのキャッシュ変数が絡みます。 どちらを設定すべきかはGeneratorによって変わります。

6.1. Makefile系のとき

GeneratorがMakefile系の場合、単一のビルドオプションしか保持できないのでConfigureのタイミングでビルドタイプを指定してやる必要があります。 ビルドタイプはキャッシュ変数CMAKE_BUILD_TYPEに値を設定することで指定できます。

cmake .. -DCMAKE_BUILD_TYPE=Release

あとは生成されたMakefileを使ってビルドするだけです。

cmake --build .

ちなみにCMakeではデフォルトでDebug, Release, MinSizeRel, RelWithDebInfoの4種類のビルドタイプが用意されています。 それぞれどのようなビルドオプションが採用されるかはコンパイラによって違うので別途調べてみてください。

6.2. IDE系のとき

GeneratorがVisual StudioXcodeといったIDE系の場合、プロジェクトファイルに複数のビルドタイプを保持できるので、Configure/Generateのタイミングでビルドタイプを指定する必要はありません。

cmake ..

代わりにビルドを実行するタイミングでどのビルドタイプを使うかを指定します。

cmake --build . --config Release

プロジェクトファイルに含めるビルドタイプをRelease, RelWithDebInfoの2つに限定したい場合はキャッシュ変数CMAKE_CONFIGURATION_TYPESRelease;RelWithDebInfoのように値を設定すればよいです。

※ この辺りはこちらのstack overflowの記事を参考にしました。


以上、CMakeにおける変数の役割とキャッシュ変数を通したプロジェクト設定の方法について紹介しました。

次の記事は外部ライブラリを利用するCMakeプロジェクトの作り方の話です⇒勝手に作るCMake入門 その4 外部ライブラリを利用する


もしよければ↓の☆を1クリックお願いします!