Weird Wired World

Programming, Security

cmake-init C++プロジェクトテンプレート

この記事は初心者C++ Advent Calendar 2017 21日目の記事です。

qiita.com

まず初めにCMakeを簡単に紹介します。CMakeはautotoolsと並んで人気のビルドツールです。やはりautotoolsと同様プラットフォームの違いを吸収してMakefileを生成します。

小規模なプロジェクトでは簡単で便利な一方で,ドキュメントやモジュール等のファイル構成を気にしだすと,設定の柔軟性から様々な選択肢があり,本当に悩めます。Qiita等にも色々と試行錯誤された投稿があるのですが,やはり独自の実装というのは気が引けますし,テストやドキュメントまで考慮されてるものは現状なさそうです。

そこで本記事では,GitHub上で広く流通していると思われるテンプレートであるcmake-initについて書きます。ライセンスはMIT。

GitHub - cginternals/cmake-init: Template for reliable, cross-platform C++ project setup using cmake.

環境: 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)のクラス一覧を見てみます:

f:id:enufranz:20171229215446p:plain:w400

最後にインストールを試してみます。

通常のインストールではバイナリは/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で独自のビルド構成を作っていたことでした。

もう少し理解が進んだらこっちを使っていきたい~