CMake チュートリアル

このチュートリアルでは,一般的なビルドシステムの問題点のうち,CMake を利用することで解決できる事項について段階的に述べていきます. Mastering CMake では,これらのトピックの多くが個別の問題として紹介されていますが, これらが1つのプロジェクトで一緒に機能する様子を知るのも勉強になるでしょう. このチュートリアルは,CMake ソースコードツリーの Tests/Tutorial ディレクトリにもあります. 各ステップは,ステップごとのチュートリアルを含むサブディレクトリが存在します.

(Step 1) 最初に

最も基本的なプロジェクトは,ソースコードから実行ファイルをビルドすることです. 単純なプロジェクトの場合,CMakeLists ファイルに必要なのは2行だけです. これが,チュートリアルのスタート地点になるでしょう.この CMakeLists ファイルは次のようになります:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

この例の CMakeLists ファイルでは,小文字のコマンドが使われている事に気を付けてください. CMake では,大文字,小文字,それらの混在のコマンドがサポートされています. tutorial.cxx のソースコードは,数字の平方根を計算するもので,この最初のバージョンは次のようにとても単純です:

// 1つの数値の平方根を計算する単純なプログラム
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

バージョン番号とヘッダファイルの追加

まず,実行ファイルとプロジェクトに,バージョン番号を付加する機能を加えます. この機能は,ソースコード内にも実装できますが,CMakeLists ファイル内で実装したほうがより柔軟になります. バージョン番号を付加するために,CMakeLists ファイルを次のように修正します:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# バージョン番号
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# CMake の設定をソースコードに渡すための
# ヘッダファイルの構成
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )

# TutorialConfig.h を検出できるように,
# インクルードファイルの探索パスにバイナリツリーを追加
include_directories("${PROJECT_BINARY_DIR}")

# 実行ファイルを追加
add_executable(Tutorial tutorial.cxx)

設定ファイルはバイナリツリー内に書き出されるので,インクルードファイルの探索パスのリストに,そのディレクトリを追加しなければいけません. そして,ソースツリー内に TutorialConfig.h.in ファイルを,次のような内容で作成します:

// Tutorialの設定オプション
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

CMake このヘッダファイルを設定する際に, @Tutorial_VERSION_MAJOR@ と @Tutorial_VERSION_MINOR@ の値は,CMakeLists ファイルの値で置き換えられます. 次に, tutorial.cxx が 設定されたヘッダファイルを読み込み,バージョン番号が使えるように修正します. ソースコードは次のようになります.

// 1つの数値の平方根を計算する単純なプログラム
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"

int main (int argc, char *argv[])
{
  if (argc < 2)
  {
    fprintf(stdout,"%s Version %d.%d\n",
            argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

主な変更点は,TutorialConfig.h ヘッダファイルのインクルードと,使い方メッセージの一部としてバージョン番号を書き出すようになった事です.

(Step 2) ライブラリの追加

さて,プロジェクトにライブラリを追加してみましょう. これは,数字の平方根を計算する実装を含む独自のライブラリです. 実行ファイルは,コンパイラが提供する標準の平方根関数の代わりに,このライブラリを利用できます. このチュートリアルでは,ライブラリを MathFunctions と呼ばれるサブディレクトリに置きます. そこには,次のように1行の CMakeLists ファイルが置かれます.

add_library(MathFunctions mysqrt.cxx)

このソースファイル mysqrt.cxx には,コンパイラの sqrt 関数と同様な機能を提供する,mysqrt と呼ばれる関数があります. 新しいライブラリを利用するには,ライブラリをビルドするにように,トップレベルの CMakeLists ファイルに add_subdirectory の呼び出しを追加します. また,MathFunctions/mysqrt.h ヘッダファイルにある関数の宣言を見つけられるように,別のインクルードディレクトリも追加します. 最後の修正は,新しいライブラリを実行ファイルに追加することです. トップレベルの CMakeLists の最後の数行は,次のようになります:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)

# 実行ファイルを追加
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)

次に,MathFunction ライブラリの作成をオプション形式にします. このチュートリアルでは,そうする意味はまったくありませんが, より巨大なライブラリの場合や,サードパーティのコードに依存するライブラリの場合は, そうしたいと思うことがあるかもしれません. 最初のステップは,トップレベルの CMakeLists ファイルにオプションを追加することです.

# 自前の演算関数を使うべきか?
option (USE_MYMATH
        "Use tutorial provided math implementation" ON)

これは,デフォルト値は ON で,ユーザが好きなように変更できるものとして,CMake GUI に表示されます. この設定はキャッシュに保存されるので,ユーザは,このプロジェクトで CMake を実行する度に,設定を行う必要はありません 次に,MathFUnctions ライブラリをビルドして,リンクするための条件を変更します. これを行うために,トップレベル CMakeLists ファイルを次のように変更します:

# MathFunctions ライブラリを追加するか?
#
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# 実行ファイルを追加
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})

これは,MathFUnctions をコンパイルして利用するかどうかを決定するために,USE_MYMATHの設定を利用します. 後で実行ファイルにリンクするオプションライブラリを収集するために,変数(この場合は EXTRA_LIBS)を利用する事に注意してください. これは,より大きなプロジェクトで,多数のオプションコンポーネントをきれいに保つための共通のアプローチです. ソースコードに対応する変更は,単純です:

// 1つの数値の平方根を計算する単純なプログラム
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n", argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }

  double inputValue = atof(argv[1]);

#ifdef USE_MYMATH
  double outputValue = mysqrt(inputValue);
#else
  double outputValue = sqrt(inputValue);
#endif

  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

このソースコードでは,USE_MYMATH を利用しています. これは,CMake からソースコードに与えられるもので,次の行を追加することで,TutorialConfig.h.in 設定ファイルを介して渡されます.

#cmakedefine USE_MYMATH

(Step 3) インストールとテスト

次は,インストール規則とプロジェクトを支援するテストを追加します.インストール規則は,実に簡単です. MathFunctions ライブラリの場合,次の2行を,MathFunctions の CMakeLists ファイルに追加することで, インストールされるライブラリとヘッダファイルを設定します:

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

ライブラリではなくアプリケーションの場合, 実行ファイルおよびヘッダファイルをインストールするために, 以下の数行が,トップレベルの CMakeLists に追加されます

# インストールターゲットを追加
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
         DESTINATION include)

これで全部です. これで,このチュートリアルをビルドし, make install と入力する(あるいは,IDE で INSTALL ターゲットをビルドする)ことで,適切なヘッダファイル,ライブラリ,実行ファイル,をインストールすることができるはずです. CMake 変数 CMAKE_INSTALL_PREFIX を利用して,ファイルがインストールされる場所のルートを指定できます. テストの追加も,非常に簡単な処理です. トップレベルのCMakeLists の最後に,アプリケーションが適切に動作していることを確認するための基本的なテストを追加できます.

# アプリケーションを実行
add_test (TutorialRuns Tutorial 25)

# 25 の平方根を計算
add_test (TutorialComp25 Tutorial 25)

set_tests_properties (TutorialComp25
  PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")

# 負の数値の扱い
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative
  PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")

# 小さい数値の扱い
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall
  PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")

# 使い方のメッセージを表示できるか?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES
  PASS_REGULAR_EXPRESSION "Usage:.*number")

最初のテストは単に,セグメンテーションフォルトや,その他のクラッシュを引き起こさずにアプリケーションが実行できることを確認します. これが,CTest テストの基本形です. それに続くいくつかのテストでは,テストの出力が特定の文字列を含むことを確認するために, PASS_REGULAR_EXPRESSION テストプロパティを利用します. この場合,計算された平方根が正しいこと, 間違った数の引数が与えられた場合に使い方メッセージが出力されること,を確認します. 異なる入力値をテストするために多くのテストを追加したい場合,次のようなマクロを考えても良いでしょう:

#簡単にテストを追加するためのマクロ定義とその利用
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)

# テスト結果
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")

1つの do_test が実行される度に,別のテストが,その名前,入力値,与えられた引数に基づく結果,と共にプロジェクトに追加されます.

(Step 4) システム検査

次は,ターゲットとするプラットフォームに存在しないかもしれない特徴に依存するコードを,我々のプロジェクトに追加することについて考えます. 今回の例では,ターゲットプラットフォームに log および exp 関数が存在するか否かに依存したコードを追加します. もちろん,これらの関数は大部分ののプラットフォームに存在しますが, このチュートリアルでは,これらの関数はあまり一般的ではないものとして扱います. もし,プラットフォームに log 関数が存在すれば,mysqrt 関数内で平方根を計算するために利用できます. まず最初に,トップレベルにあるCMakeLists ファイル にある CheckFunctionExists.cmake マクロ を利用して,これらの関数の機能をテストします.

# does this system provide the log and exp functions?
include (CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

次に,CMake が,プラットフォームでこれらを検出した場合,これらの値を定義するために TutorialConfig.h.in を次のように修正します:

// このプラットフォームに exp と log はあるか?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

TutorialConfig.h に対する configure_file コマンドよりも前に,log と exp のテストが実行されることが重要です. この configure_file コマンドは,CMake にある現在の設定を用いて即座に configure を行います. 最後に,このシステム上でそれらの関数が有効ならば,次のようなコードを利用して, mysqrt 関数内で log と exp に基づく別の実装を与えることができます:

// もし log と exp が両方あれば,それを利用する
#if defined (HAVE_LOG) && defined (HAVE_EXP)
  result = exp(log(x)*0.5);
#else // なければ反復計算
  . . .

(Step 5) ファイルを生成するファイルと,生成されたファイルの追加

このセクションでは,生成されたソースファイルをアプリケーションのビルドプロセスに組み込む方法について述べます. この例では,ビルドプロセスの一部として,事前に計算された平方根のテーブルを作成し,アプリケーションに組み込んでコンパイルします. このためには,まずテーブルを生成するプログラムが必要です. MathFunctions サブディレクトリにある,MakeTable.cxx という名前の新しいソースファイルそれです.

// 1つの数値の平方根を計算する単純なプログラム
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main (int argc, char *argv[])
{
  int i;
  double result;

  // 必要な引数を指定するように
  if (argc < 2)
    {
    return 1;
    }

  // 出力ファイルを開く
  FILE *fout = fopen(argv[1],"w");
  if (!fout)
    {
    return 1;
    }

  // 平方根テーブルを持ったソースコードを作成
  fprintf(fout,"double sqrtTable[] = {\n");
  for (i = 0; i < 10; ++i)
    {
    result = sqrt(static_cast<double>(i));
    fprintf(fout,"%g,\n",result);
    }

  // テーブルの最後を0にする
  fprintf(fout,"0};\n");
  fclose(fout);
  return 0;
}

このテーブルは,有効な C++ コードとして与えられており, 出力を書き出すためのファイル名は,引数で指定されることに注意してください. 次のステップでは,MakeTable 実行ファイルをビルドするために, 適切なコマンドを MathFunctions の CMakeLists ファイルに追加し,それをビルドプロセスの一部としてを実行します. このために新しいコマンドが必要であり,それは以下のようになります:

# まず,テーブルを生成するための実行ファイルを追加
add_executable(MakeTable MakeTable.cxx)

# ソースコードを生成するためのコマンドを追加
add_custom_command (
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  )

# インクルードファイルの探索パスに,
# バイナリツリーディレクトリを追加
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )

# メインライブラリを追加
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )

まず,MakeTable 実行ファイルが,その他の実行ファイルと同様に追加されます. そして,MakeTable を実行することで Table.h を生成する方法を指定する,カスタムコマンドを追加します. 次に,CMake に,mysqrt.cxx が生成されたファイル Table.h に依存していることを知らせます. これは,MathFunctions ライブラリのソースコードリストに生成された Table.h を追加するだけです. また,Table.h が検出され mysqrt.cxx にインクルードされるように, インクルードディレクトリのディレクトリリストに現在のバイナリディレクトリを追加する必要もあります.

このプロジェクトをビルドする際,MakeTable 実行ファイルが最初にビルドされます. そして,Table.h を生成するために MakeTable が実行されます. 最後に,Table.h をインクルードする mysqrt.cxx がコンパイルされ MathFunctions ライブラリが生成されます.

これで,今まで追加してきた特徴の全てを備えたトップレベルの CMakeLists ファイルは,次のようになります:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)

# バージョン番号
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# このシステムは log と exp 関数を提供しているか?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)

check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

# 自前の演算関数を使うべきか?
option(USE_MYMATH
  "Use tutorial provided math implementation" ON)

# CMake の設定をソースコードに渡すための
# ヘッダファイルの構成
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )

# TutorialConfig.h を検出できるように,
# インクルードファイルの探索パスにバイナリツリーを追加
include_directories ("${PROJECT_BINARY_DIR}")

# MathFunctions ライブラリを追加するか?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# 実行ファイルを追加
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})

# インストールターゲットを追加
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
         DESTINATION include)

# アプリケーションを実行
add_test (TutorialRuns Tutorial 25)

# 使い方のメッセージを表示できるか?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES
  PASS_REGULAR_EXPRESSION "Usage:.*number"
  )


#簡単にテストを追加するためのマクロ定義
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endmacro (do_test)

# do a bunch of result based tests
do_test (4 "4 is 2")
do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645")
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
do_test (0.0001 "0.0001 is 0.01")

TutorialConfig.h は,次のようになります:

// Tutorialの設定オプション
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH

# このシステムは log と exp 関数を提供しているか?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
And the CMakeLists file for MathFunctions looks like:

# まず,テーブルを生成するための実行ファイルを追加
add_executable(MakeTable MakeTable.cxx)
# ソースコードを生成するためのコマンドを追加
add_custom_command (
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  )
# インクルードファイルの探索パスに,
# バイナリツリーディレクトリを追加
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )

# メインライブラリを追加
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

(Step 6) インストーラの作成

次に,プロジェクトを他の人に配布して使ってもらえるようにしたい,という場合を考えます. 我々は,様々なプラットフォーム向けのバイナリとソースの両方を配布したいと考えています. 前述の「(Step 3) インストールとテスト」のセクションでは, ソースコードからビルドされたバイナリをインストールしましたが,それとは少し異なります. この例では,cygwin,debian,RPMs,などにあるような, バイナリインストールとパッケージ管理をサポートするインストールパッケージをビルドします. このために,「CPack を使ったパッケージ」の章で述べられるように, CPack を利用してプラットフォーム固有のインストーラを作成します. 具体的には,トップレベルの CMakeLists.txt ファイルの最後に数行追加します.

# CPack ドリブンなインストーラパッケージの作成
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
     "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include (CPack)

これで全部です. まず, InstallRequiredSystemLibraries をインクルードすることから始まります. このモジュールは,現在のプラットフォームにおいてプロジェクトが必要とする任意のランタイムライブラリをインクルードします. そして,このプロジェクトのライセンスとバージョン情報を格納した場所をCPack 変数を設定します. このバージョン情報は,このチュートリアルで以前に設定した変数を利用します. 最後に,インストーラを設定するために,これらの変数とユーザシステムの機能を利用する CPack モジュールをインクルードします.

次のステップでは,このプロジェクトを通常の方法でビルドし,そのうえで CPack を実行します. バイナリ配布物を作成するには,次のようにします:

cpack -C CPackConfig.cmake

ソース配布物を作成するには,次のようにします:

cpack -C CPackSourceConfig.cmake

(Step 7) ダッシュボード支援機能の追加

テスト結果をダッシュボードに提出するための支援機能を追加するのは,とても簡単です. 我々は既に,このチュートリアルで,プロジェクトのための多くのテストを定義しました. あとは,それらのテストを実行して,ダッシュボードに提出すれば良いだけです. ダッシュボード支援機能をインクルードするには, トップレベル CMakeLists ファイルに,CTest モジュールをインクルードします.

# ダッシュボードスクリプティングを有効に
include (CTest)

ダッシュボードにおけるプロジェクト名を指定することができる CTestConfig.cmake ファイルも作成します.

set (CTEST_PROJECT_NAME "Tutorial")

CTest は,実行時にこのファイルを読み込みます. 簡単なダッシュボードを作成するには,プロジェクトで CMake を実行して, バイナリツリーにディレクトリを移動し,ctest -D Experimental を実行します. このダッシュボードの結果は, ここの Kitware のパブリックダッシュボードにアップロードされます.