こんにちは。田原です。

C++は大変多くのプラットフォームで使える高生産性な開発ツールです。しかし、マルチ・プラットフォーム対応のビルド・ツールは多くはないため、C++を使っても意外にマルチ・プラットフォームなプログラムの開発には苦労します。その苦労をかなり軽減してくれるツールがCMakeです。そこで、今回からCMake編としてCMakeの使い方を解説していきたいと思います。

なお、今回は、説明の都合上、以前解説したCMakeの基礎とTheolizerの組み込み方から抽出したので前半はほとんど重複してます。ごめんなさい。

1.CMakeの基本

CMake編のインデックス・ページで基本的な説明をしていますので、もしまだご覧になっていない方は是非ざっと目を通して下さい。

ビルドについて
ビルドの基本についてCMakeの基礎とTheolizerの組み込み方の「1.予備知識」にて、私なりに説明しています。興味のある方は参考にされて下さい。

1-1.CMakeのダウンロードとインストール方法

当技術ブログの「実践C++入門講座」にて解説していますので、以下を参照下さい。

Windowsの場合
Linux(ubuntu)の場合

1-2.一番簡単なCMakeLists.txt

CMakeはCMakeLists.txtを解釈してビルド・システム(いわゆる Makefile のことです)を自動生成します。そして、最も簡単なCMakeLists.txtは本当に簡単です。
add_executable()文にて、コンパイルするソース・ファイルの名前と生成する実行ファイルの名前を指定するだけです。

add_executable(main main.cpp)

add_executable()は実行ファイルを生成するよう指示します。
最初のmainが生成する実行ファイル名です。拡張子はOSによって異なるのでここには記述しません。
その次のmain.cppがコンパイルするソース・ファイル名です。複数記述できます。

早速hello worldを作ってみましょう。

#include <iostream>
int main()
{
    std::cout << "Hello, World!!\n";
}

main.cppとCMakeLists.txtを同じフォルダに保存して下さい。

次にMakefileやプロジェクト・ファイルを生成しますが、ソースと入り交じるとソース・ファイルの管理が面倒な事になりますので、ビルド専用のフォルダを用意することを強くお薦めします。
ここでは、お手軽にmain.cppとCMakeLists.txtを保存しているフォルダの下にビルド専用フォルダを作ってみます。

main.cppのあるフォルダがカレント・フォルダの時、次のコマンドでgcc用のMakefileやVisual C++用のプロジェクト・ファイルを生成できます。

mkdir build
cd build
cmake ..

linuxでもWindowsのVisual C++でも同じコマンドでできてしまいます。ちょっとびっくりです。
さて、実はcmake ..は手抜きコマンドです。本来はどのコンパイラ向けに生成するのかを-Gオプション(ジェネレータ)を指定します。

cmake -Gで使用可能なジェネレータのリストが出力されます。

Windowsでの実行例
 >cmake -G
 CMake Error: No generator specified for -G

 Generators
   Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                  Optional [arch] can be "Win64" or "ARM".
   Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
                                  Optional [arch] can be "Win64" or "ARM".
   Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
                                  Optional [arch] can be "Win64" or "ARM".
   Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.
                                  Optional [arch] can be "Win64" or "ARM".
   Visual Studio 10 2010 [arch] = Generates Visual Studio 2010 project files.
                                  Optional [arch] can be "Win64" or "IA64".
   Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.
                                  Optional [arch] can be "Win64" or "IA64".
   Visual Studio 8 2005 [arch]  = Generates Visual Studio 2005 project files.
                                  Optional [arch] can be "Win64".
   Visual Studio 7 .NET 2003    = Deprecated.  Generates Visual Studio .NET
                                  2003 project files.
   Borland Makefiles            = Generates Borland makefiles.
   NMake Makefiles              = Generates NMake makefiles.
   NMake Makefiles JOM          = Generates JOM makefiles.
   Green Hills MULTI            = Generates Green Hills MULTI files
                                  (experimental, work-in-progress).
   MSYS Makefiles               = Generates MSYS makefiles.
   MinGW Makefiles              = Generates a make file for use with
                                  mingw32-make.
   Unix Makefiles               = Generates standard UNIX makefiles.
   Ninja                        = Generates build.ninja files.
   Watcom WMake                 = Generates Watcom WMake makefiles.
   CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
   CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
   CodeBlocks - NMake Makefiles JOM
                                = Generates CodeBlocks project files.
   CodeBlocks - Ninja           = Generates CodeBlocks project files.
   CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
   CodeLite - MinGW Makefiles   = Generates CodeLite project files.
   CodeLite - NMake Makefiles   = Generates CodeLite project files.
   CodeLite - Ninja             = Generates CodeLite project files.
   CodeLite - Unix Makefiles    = Generates CodeLite project files.
   Sublime Text 2 - MinGW Makefiles
                                = Generates Sublime Text 2 project files.
   Sublime Text 2 - NMake Makefiles
                                = Generates Sublime Text 2 project files.
   Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
   Sublime Text 2 - Unix Makefiles
                                = Generates Sublime Text 2 project files.
   Kate - MinGW Makefiles       = Generates Kate project files.
   Kate - NMake Makefiles       = Generates Kate project files.
   Kate - Ninja                 = Generates Kate project files.
   Kate - Unix Makefiles        = Generates Kate project files.
   Eclipse CDT4 - NMake Makefiles
                                = Generates Eclipse CDT 4.0 project files.
   Eclipse CDT4 - MinGW Makefiles
                                = Generates Eclipse CDT 4.0 project files.
   Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
   Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.

例えば、Visual Studio 2015で64ビット・プログラム用のプロジェクトを生成する時は、次のコマンドです。

cmake -G "Visual Studio 15 2015 Win64" ..

1-3.project文

プロジェクト名を決定するproject文があります。
Visual Studioではソリューション名になります。Visual Studioの場合は次のように対応するようです。

CMake Visual C++
project ソリューション
add_executable
add_library
add_custom_target
プロジェクト

例えば、以下のように「プロジェクト名(Visual Studioのソリューション名になります)」を指定します。

project(example)
add_executable(main main.cpp)

project文の必要性は実は良く分かっていません。使用するプログラミング言語やアプリのバージョンを記述できます。私自身は主に Visual Studio のソリューション名を指定するために使っています。

なお、上記のMakefile生成処理の際に「お試しコンパイル」が走っているのにお気づきかと思いますが、project文があるとproject文のところで「お試しコンパイル」されています。project文がない時は CMakeLists.txt 処理の先頭で「お試しコンパイル」が行われるようです。
また、project文で各種のCMAKE変数が設定されます。project文がなかったらCMakeLists.txtの先頭でそれらが行われるようです。(ただそれだけの話ですが。)

1-4.コンパイル・オプションを指定する

デフォルトでは警告はあまりでません。ですが、できるだけ警告は有効にした方が無駄なデバッグに苦労しなくて済みます。

例えば、次の6行目はinと20を比較しようとして代入してます。結構やりがちなバグです。
警告オプションを指定しない場合、警告はでません。

#include <iostream>
int main()
{
    int in;
    std::cin >> in;
    if (in = 20)
    {
        std::cout << "OK\n";
    }
}

できるだけ警告を出すようにコンパイル・オプションを指定してみます。

project(example)
add_executable(main main.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
project(example)
add_executable(main main.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

Visual C++もgccも警告が出るようになりました。
下手に悩まずとも警告を潰しているだけでそこそこデバッグできます。
もちろん警告を潰すだけではデバッグは完了しませんが、警告があるとかなりデバッグ作業が捗ります。

1-5.マルチ・プラットフォームに対応する

前節ではVisual Studio用とgcc用のCMakeLists.txtを分けていました。
しかし、大半は同じものですので、両方に対応するCMakeLists.txtを1つ提供できれば便利です。
前節のようなケースでは、Visual Studioかgccかを判別してCMAKE_CXX_FLAGSの設定を切り替えることができるとうまくいきます。

まず、CMakeはC++の#if/#else/#endifのような構文があります。次のように書きます。

if(条件)
elseif(条件)
else()
endif()

elseやendifにも()が必要なことと、#が不要な点が異なります。
CMakeの#はコメント行です。私は時々#ifや#endifと書いてしまってCMakeエラーに見舞われます。皆さんもご注意下さい。
なお、()を忘れるとエラーになります。
また、elseif()とelse()はオプションです。不要な時は省略して良いです。

そして、ここに条件に指定できる変数のリストがあります。

例えば、Visual C++(MSVC)とその他でCMAKE_CXX_FLAGSの設定を切り替える場合、次のように書きます。

project(example)
add_executable(main main.cpp)
if (MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
endif()

2.スクリプト・モード

いきなり第一回目にスクリプト・モードを説明する CMake の解説は珍しいと思います。しかし、先に説明した方が理解しやすいのではないかと考えています。(ちょっとドキドキですが。)また、スクリプト・モードは要するにインタプリタのようなものですから、走らせて結果が分かるのが早いです。お手軽にお試しできるので学習効率が上がります。なので、最初から解説してみます。

さて、CMakeは実は広い意味でのプログラミング言語です。Windowsのコマンド・プロンプトやlinuxのbashなどとよく似た機能を持っています。ビルド・システム生成に特化しているので一般的な処理はコマンド・プロンプトやbash程は書きやすくは無いですが、機能的にはこれらとほぼ同等です。
更にconfigure_fileのようなテキスト・ファイル生成機能も内蔵しているため、バージョン番号の反映等でも威力を発揮します。

ビルド・システムを生成する専用の一部のコマンド(project文やadd_excute文など)は、スクリプト・モードでは使えません。逆に、ビルド・システムを生成する際には全てのコマンドを使うことができます。
つまり、コマンドは大きく2種類に分かれています。(短縮のため、ビルド・システム=Makefileと記載しています。)

コマンドの種別 Makefile生成 スクリプト・モード 効果タイミング
Makefile生成専用コマンド 使用可 使用不可 Makefile生成時~ビルド時
スクリプト・コマンド 使用可 使用可 Makefile生成時と
スクリプト・モード時

①は主に-Gオプション(もしくは-Pオプション無し)で起動します。②は-Pオプションで起動します。

さて、CMakeの理解を難しくしている要因の1つに、①のコマンドと②のコマンドに一見よく似た物があることです。(特に、add_custom_commandexecute_process
しかし、上記のように効果を及ぼすタイミングが決定的に異なりますので、両者は互いに相手と交換できません。
ですので、それぞれについて常に意識するよう務めると理解が早くなると思います。

2-1.スクリプト・ファイル

ビルド・システム生成は、CMakeLists.txtというファイルに設定します。(1つのフォルダに1つしか存在する必要がないため、このファイル名は変更できません。)
スクリプト・モード用のスクリプト・ファイルは1つのフォルダの配下に複数用意することがあるので名前を自由に付けることができます。拡張子は.cmakeに決っているようです。(他の拡張子を付けても呼び出せますが、慣習に従って.cmakeにしておいたほうが良いと思います。)

2-2.スクリプト・モードの起動方法

基本は以下の通りです。(スクリプト・ファイルがあるフォルダをカレント・フォルダとします。)

$ cmake -P スクリプト・ファイル名

パラメータを渡すことができます。CMake変数をコマンドラインで定義するイメージになります。

$ cmake -D変数名=変数値 -D変数名=変数値 ・・・ -P スクリプト・ファイル名

-Pオプションより後ろに書いてもスクリプト内に渡りません。エラーにもならないのでご注意下さい。
つまり、以下のように書いても各変数は定義されません。

$ cmake -P スクリプト・ファイル名 -D変数名=変数値 -D変数名=変数値 ・・・

2-3.コマンドの基本

C++のif文のような文は、CMakeではコマンドと呼ばれています。
上記の公式のリファレンス・ページでコマンドは下記の4つに分類されています。

  • Scripting Commands
    上述の「②スクリプト・コマンド」です。ビルド・システム生成時とスクリプト・モード時に使えます。
  • Project Commands
    上述の「①Makefile生成専用コマンド」です。ビルド・システム生成時のみ使えます。
  • CTest Commands
    CMakeにはCTestというテスト・ツールがあります。そのテスト・モード時のみ使えるコマンドです。
    CTest自体は使っているのですが、これらの専用コマンドは使ったことがありません。よく判ってないので当面当講座では解説しません。ごめんなさい。
  • Deprecated Commands
    いつかなくなるものですので使わない方が良いコマンド群です。当講座でも解説しない予定です。
2-3-1.messageコマンド

スクリプト・コマンドの1つにmessageコマンドがあります。

かなり柔軟な書き方ができるのですが、まずは以下のような使い方で覚えるのが良いと思います。

message("メッセージ")

他に、次のような書き方もよく使います。

message(STATUS "メッセージ")
message(SEND_ERROR "エラー・メッセージ")

後者は、エラー発生場所とエラー・メッセージを表示します。

実行例
message(STATUS "メッセージ")
message(SEND_ERROR "エラー・メッセージ")
message("通常メッセージ")
>cmake -P sample.cmake
-- メッセージ
CMake Error at sample.cmake:2 (message):
  エラー・メッセージ


通常メッセージ

なお、文字列は、原則として””で括ることをお勧めします。必ずしも括らない方が良い時もありますが、変数の様々な振る舞いを学んでから必要に応じて使った方が良いと思います。最初の頃は文字列は括っておくと思わぬ振る舞い(間にスペースが入った時など)が減るので安全なのです。

2-3-2.コマンド名

コマンドは大文字小文字を区別しません。例えば、以下は全く同じ動作をします。

message("メッセージ")
MESSAGE("メッセージ")

以下のように記述してもOKでした。

Message("メッセージ")
meSSage("メッセージ")

後者は言うまでもなくやめておくべきですね。
前者もあまり見かけない記述ですので避けておいた方が良いかもしれません。全部小文字、もしくは、全部大文字のどちらかをよく見かけます。

2-3-3.パラメータ

CMakeのコマンドはmessageコマンドのように複数の可変個のパラメータを受け取るものが多いです。
パラメータは空白で区切ります。””で括られたパラメータは””の文字列の間に空白が有っても空白を含めて1つのパラメータとして解釈されます。

message("文字列1"    "文字列2")
message("文字列1      文字列2")
実行例
message("文字列1"    "文字列2")
message("文字列1      文字列2")
>cmake -P sample.cmake
文字列1文字列2
文字列1      文字列2

前者は文字列1と文字列2がそれぞれ別のパラメータ(文字列)として解釈されます。間の空白は単なる区切り記号として無視されるため、2つの文字列が繋がって表示されます。
後者は「文字列1 文字列2」が1つのパラメータ(文字列)として解釈されます。そのため、間の空白は区切り記号ではなく文字列の一部ですので無視されずに表示されます。

2-3-4.複数行に渡るコマンド

1つのコマンドは1行で書くことも多いですが、コマンドによってはとても収まらないので複数行に分けて書くこともあります。
コマンドは、コマンド名(パラメータ群)で1つですから、最後の)までを複数行に分けることができます。
なお、コマンド名内の改行は当然NGですが、コマンド名と(の間の改行もNGです。(空白はOK)

message (
    "1行目"
    "2行目"
    "3行目"
)
>cmake -P sample.cmake
1行目2行目3行目

2-4.変数の設定と参照方法の基本

CMakeはユーザ定義変数、事前定義定数、生成時自動定義変数など、さまざまな「変数」があります。
定数は設定や変更ができないだけで参照方法は変数と同じです。

設定はsetコマンドやCMake -Pで起動する際の-Dオプションを使います。${変数名}で変数の中身を取り出(参照)します。

set(WORLD "World!")
message(${HELLO} " " ${WORLD})
message("${HELLO} ${WORLD}")
>cmake -DHELLO=Hello -P sample.cmake
Hello World!
Hello World!

C++に慣れていると前者のmessageコマンドのような書き方をついしてしまうと思いますが、後者のような書き方もできます。C++のような言語との大きな違いは””の中にある変数参照も展開されることです。
この振る舞いはコマンド・プロンプトやbashと同様です。CMakeはスクリプト言語ということですね。

3.まとめ

今回はCMakeの触りの部分を解説しました。CMakeは一種のプログラミング言語ですが、モードが2つあります。1つはビルド・システム(=Makefile)生成モード、もう一つはスクリプト・モードです。
前者はCMakeの最大の機能であり様々なプラットフォームでメジャーなビルド・ツール用の設定ファイル(Makefile等)を自動生成します。
後者はWindowsのコマンド・プロンプトやlinuxのbashのようなものです。
これら2つを組み合わせることで非常に強力、かつ、マルチ・プラットフォームなビルド・システムを提供することが可能になります。
その分これらの相違を意識して理解しないと結構混乱しますので、ドキドキしつつ最初の解説に含めてみました。

さて、次回はよく使うコマンドについて解説する方向で考えています。少し間があくと思いますが、よろしくご期待のほどお願いします!