cmake-init C++プロジェクトテンプレート
この記事は初心者C++ Advent Calendar 2017 21日目の記事です。
まず初めにCMakeを簡単に紹介します。CMakeはautotoolsと並んで人気のビルドツールです。やはりautotoolsと同様プラットフォームの違いを吸収してMakefileを生成します。
小規模なプロジェクトでは簡単で便利な一方で,ドキュメントやモジュール等のファイル構成を気にしだすと,設定の柔軟性から様々な選択肢があり,本当に悩めます。Qiita等にも色々と試行錯誤された投稿があるのですが,やはり独自の実装というのは気が引けますし,テストやドキュメントまで考慮されてるものは現状なさそうです。
そこで本記事では,GitHub上で広く流通していると思われるテンプレートであるcmake-initについて書きます。ライセンスはMIT。
環境: Ubuntu 16.04 64bit,cmake-init: 36de51
テンプレートを試す
テンプレートはフィボナッチ数列を計算・表示するプロジェクトになっています。このプロジェクトは共有ライブラリのビルド・結合,テスト,ドキュメントの生成を行っており,これらの書き方を学ぶことができます。それでは見ていきます。
まずは導入です。
sudo apt-get install -y git cmake doxygen graphviz
git clone https://github.com/cginternals/cmake-init
設定ファイルのディレクトリ.localconfig/
を生成します。
./configure
全ての機能を試すために,.localconfig/default
に次の設定を加えます。なおこの設定はcmake-initの用意する,一時的な設定を適用するための方法です。詳細は「一時的な設定」の項で紹介します。
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DOPTION_BUILD_DOCS:BOOL=ON" CMAKE_OPTIONS="${CMAKE_OPTIONS} -DOPTION_BUILD_EXAMPLES:BOOL=ON"
ビルドします。Windowsではgitに付属するgit-bashで実行できます。
./configure
cmake --build build
ここで次の2つのトラブルシューティングを書いておきます:
- 1. Qt5のWarningが出る場合
- 差し当たり無視して大丈夫です。
- モジュールの1つであるfibguiがQt5に依存しています。Qt5がないとこのビルドは無視され全体のビルドには影響はないので,今回は扱わないことにしたいと思います。
- 2. doxygenのdotに関するエラーが出る場合
- graphvizをインストールしてください。
以上の操作により,build/以下に実行バイナリ,依存ライブラリ2つ,テスト用バイナリ,及びドキュメントのビルドが完了しました。
これらのファイルの概要は次の通りです:
名前 | 種類 | 説明 |
---|---|---|
libfiblibfib.so | 共有ライブラリ | フィボナッチ数列の適当な番号を表示する |
libbaselib.so | 共有ライブラリ | data/DATA_FOLDER.txtの内容を読んで表示する |
fibcmd | 実行バイナリ | baselib, fiblibを結合して実行する |
fiblib-test | 実行バイナリ | Google Testによるテストを実行する |
docs/ | ディレクトリ | doxygenの生成結果 |
生成されるドキュメント(build/docs/api-docs/html/index.html)のクラス一覧を見てみます:
最後にインストールを試してみます。
通常のインストールではバイナリは/usr/[local/]bin
,リソースファイルは/usr/[local/]share/
に配置されます。
cd build
sudo make install
以上でインストールできた...はずです。
しかし不明点が1つ。ライブラリをLD_LIBRARY_PATHに追加しておけば起動はできます。が,リソースディレクトリの指定方法が不明で,実行時にDATA_FOLDER.txtの読み込みエラーになってしまいます。
enuf@ubuntu:~$ export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH enuf@ubuntu:~$ fibcmd Library template::baselib ======================================== Version: 2.0.0 Library type: SHARED Data path: data Data directory access ======================================== Reading from 'data/DATA_FOLDER.txt': Unable to open file. Fibonacci library ======================================== CTFibonacci(6) = 8 Fibonacci(8) = 21
自身のプロジェクトに適用する
大まかな変更手順は./ADAPT
(テキストファイル)に書かれています。
プロジェクト全体の設定
最上位のCMakeLists.txtで行います。プロジェクト立ち上げ時に行い大体後はそのままでしょう。
主な設定項目はプロジェクト名,バージョン,ライブラリのビルド方法,テストのビルドの有無等です。
後は各開発者が必要に応じて,一時的な設定ファイル(次項)で設定を上書きする形になります。
一時的な設定
./configure
を実行して生成される.localconfig/以下のシェルスクリプトに設定を記述します。
.localconfig/以下に分離しておくことにより,レポジトリのCMakeLists.txtを変更しなくて良いように設計されています。
主な用途はビルドタイプの設定(Debg, Release),テストをビルドするかのCMakeオプションの指定,環境変数の設定などです。記述例を.localconfig/defaultから抜粋します:
BUILD_TYPE="Release" # Disable tests #CMAKE_OPTIONS="${CMAKE_OPTIONS} -DOPTION_BUILD_TESTS:BOOL=OFF" # Qt #export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/opt/Qt5.2.1/5.2.1/gcc_64/"
CMAKE_OPTIONSの設定は./CMakeLists
モジュールの追加
モジュール(mylib)の追加をチュートリアル形式で進めます。
mylibは足し算を行うsum関数を持ち,fibcmdからリンクして実行されるとします。
下準備
まずはモジュールを追加することを宣言します。./template-config.cmake
を開きます。
set(MODULE_NAMES baselib fiblib mylib )
続いてsource/CMakeLists.txt
も同様に設定します。
add_subdirectory(mylib)
mylibモジュールの作成
次にmylib用のディレクトリを,他のモジュールを参考に作ります。ここでCMakeLists.txtはbaselibからコピーしたものです。
作成するディレクトリツリーは次の通りです:
mylib/ ├── CMakeLists.txt ├── include │ └── mylib │ └── mylib.h └── source └── mylib.cpp
mylib.hを次のものです:
// source/mylib/include/mylib/mylib.h #pragma once <mylib/mylib_api.h> MYLIB_API int sum( int a, int b );
mylib_api.hはsource/codegeneration/template_api.h.in
を元にしてビルド時に作られるヘッダです。
MYLIB_APIマクロは# define MYLIB_API attribute((visibility("default")))
と定義されており,APIとして外部に公開することを表すようです。これがないとリンクエラーになります。
mylib.cppは次のものです:
// source/mylib/source/mylib.cpp #include <mylib/mylib.h> int sum( int a, int b ) { return a + b; }
mylibのCMakeLists.txtを編集し,モジュール名,ヘッダ・ソースのパスを編集します。
# source/mylib/CMakeLists.txt # (前略) set(target mylib) # (中略) set(headers ${include_path}/mylib.h ) set(sources ${source_path}/mylib.cpp )
以上でライブラリmylibのビルド準備が整いました。
examplesモジュールの設定
続いてリンクをする主体であるexamplesモジュールを編集します。
CMakeLists.txtを編集し,mylibへのリンクを記述します。
# source/examples/fibcmd/CMakeLists.txt # (前略) target_link_libraries(${target} PRIVATE ${DEFAULT_LIBRARIES} ${META_PROJECT_NAME}::baselib ${META_PROJECT_NAME}::fiblib ${META_PROJECT_NAME}::mylib )
更にmain.cppを編集し,mylibのsum関数を呼び出します。
// source/examples/fibcmd/main.cpp // (前略) #include <mylib/mylib.h> // (中略) std::cout << "sum(1, 2): " << sum(1, 2) << std::endl; return 0; }
以上で結合が完了しました。
追加した部分の標準出力は次のようになります:
sum(1, 2): 3
結論
cpp-initはお手軽という訳ではないですが,柔軟な設定ができ実用的であるという感触を得ました。
まあ不明点があって断言はできないのですが( ̄  ̄;)
良い点
- すぐに使えること
- 初期状態ですぐに各種ライブラリ,ドキュメント,テストの生成ができる状態になっている
- Out of Sourceビルドができること
- Out of Sourceビルドができるためビルド時にリポジトリを汚さない
- また煩わしい
mkdir build && cd build && cmake .. && make
が必要ない - 大規模なプロジェクトにも対応できる
- 通常のCMakeではモジュール間のリンクが手間(add-subdirectoryが並列ディレクトリに適用できないため)だが,これが容易
悪い点
- 設定の敷居が少し高い
- 理由1. 一部に高度なCMakeの知識が必要なこと
- 理由2. ドキュメントが少ない
- CMakeLists.txtの記述の重複が多いこと
- モジュールごとにディレクトリを分けるタイプの構成だとCMakeLists.txtに重複する記述が多く発生する
- 柔軟性を担保するためかもしれないが,ここはプロジェクトごとに改善しても良さそう
おわりに
実は本調査のきっかけは,業務でプロジェクトを立ち上げた際にCMakeで独自のビルド構成を作っていたことでした。
もう少し理解が進んだらこっちを使っていきたい~