【モダンCMake】つくったC++ライブラリを簡単にインストールできるようにする

## 概要
CMakeを用いて、C/C++製のライブラリをインストール可能にし、別のCMakeプロジェクトやCMake管理外のC/C++コードから容易に利用できるようにするような方法の紹介です。皆のライブラリに`make install`ターゲットを追加しましょう。

## ~~背景だったもの~~ポエム
ポエムになった。ここは読み飛ばして構いません。C++製のプロジェクトのビルドツールとしてはCMakeがデファクトスタンダードの一つとしての位置を占めているのではないかと思います。しかし、その独特で自由度の高い表現からややとっつきにくく、また、パッケージマネージャ的な要素の薄さ[^poac]から、C++でライブラリを製作しても、それを他のプロジェクトに向け公開する事は少しハードルが上がるように思われます。せっかく製作された素晴らしいライブラリが、他のプロジェクトから利用しづらく、日の目を見る事ができないのは良い事ではないです。また、ライブラリの充実度は言語の特徴を決める重要なファクターになるでしょう。この投稿が多少でもライブラリの製作者及びユーザの助けになれれば幸いです。

[^poac]: めっちゃ余談ですがpoacというrustのcargoっぽいのが開発中らしいです。普及したらいいな。[poacpm/poac](https://github.com/poacpm/poac/tree/develop)

## やっていきましょう
例として以下のようなヘッダオンリーのプロジェクトを扱います。

```
mylib/
├── CMakeLists.txt
└── include
    └── mylib
        └── hello_world.hpp
```

簡単な`CMakeLists.txt`は以下のような形になると思われます。（このあたりについては他の情報を参照してください）

```cmake:mylib/CMakeLists.txt
cmake_minimum_required(VERSION 3.8)

project(mylib VERSION 0.1.0 LANGUAGE CXX)

# INTERFACE、コンパイルを要しない（ヘッダオンリー）
add_library(mylib INTERFACE)

# c++11以上を要求
target_compile_features(mylib INTERFACE cxx_std_11)

# インクルードパスに以下を追加する
target_include_directories(mylib INTERFACE
	${CMAKE_CURRENT_SOURCE_DIR}/include)
```

### パッケージをエクスポートする
CMakeでライブラリを他のプロジェクトに公開するには大きく二つの手法が考えられます。一つは`add_subdirectory`を使う方法です。これは上に書いたような`CMakeLists.txt`のみですぐに利用できます。`mylib/`を配下に持った別のプロジェクトから`add_subdirectory(mylib)`とする事で、`mylib/CMakeLists.txt`内で定義されたターゲットを利用できるようになります。

```cmake:another/CMakeLists.txt
add_subdirectory(mylib)

add_executable(your-exe main.cpp)
target_link_libraries(your-exe mylib)
```

もう一つが、グローバルな環境にパッケージをインストールする方法です。これによって、子ディレクトリに`mylib/`を持っていないプロジェクトからも`find_package(mylib)`のようにしてmylibが利用可能になる他、CMakeを利用していないコードからライブラリを使う事も容易になります。

パッケージのインストールは、パッケージの利用に必要な情報が記録されたファイル、`<package-name>-config.cmake`あるいは`<PackageName>Config.cmake`と、必要なリソース（ヘッダファイルやビルド済ライブラリ等）を適切なディレクトリ[^fpkg]に配置する事で達成されます。

[^fpkg]: [find_packageの動作 - Qiita](https://qiita.com/osamu0329/items/bd3d1e50edf37c277fa9)

このために上の`mylib/CMakeLists.txt`を編集していきましょう。

#### (1)インクルードパスの変更
ひとたびパッケージがグローバルにインストールされれば、もはや`${CMAKE_CURRENT_SOURCE_DIR}/include`はプロジェクトとは無関係です。（`${CMAKE_CURRENT_SOURCE_DIR}`は依然としてローカルの`mylib/`を参照する為。）通常のビルドとインストール後のビルドで異なるインクルードパスを指定するように、`target_include_directories`のパラメータを変更します。

```cmake:mylib/CMakeLists.txt
# --
# target_include_directories(mylib INTERFACE
# 	${CMAKE_CURRENT_SOURCE_DIR}/include)
# ++
target_include_directories(mylib INTERFACE
	# 通常のビルドで使われるインクルードパス
	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
	# インストールされたパッケージを利用するとき使われるインクルードパス
	$<INSTALL_INTERFACE:include>)
```

`$<INSTALL_INTERFACE:...>`の指定はインストール時にのみ有効になり[^gexp]、`${CMAKE_INSTALL_PREFIX}/`がプレフィックスとして付与されます。`${CMAKE_INSTALL_PREFIX}`のデフォルト値は`/usr/local`（Windowsでは`C:/Program Files`）です。

[^gexp]: Generator Expressionsと呼ばれる機構です。[CMake: Generator Expressions - Qiita](https://qiita.com/mrk_21/items/57075ce36f49ce0aacf4#%E3%81%82%E3%82%8B%E7%8A%B6%E6%B3%81%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AE%E3%81%BF%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B)

#### (2)configの生成
前述した`*-config.cmake`ファイルを生成します。`install(TARGETS ... EXPORT ...)`を用いる事で行なえます。　

```cmake:mylib/CMakeLists.txt
# ++
	# エクスポートしたいターゲットの名前
install(TARGETS mylib
	# 生成するconfigファイルの名前
	EXPORT mylib-config)
	# インクルードパスの指定、前項で指定済なので不要
# 	INCLUDES DESTINATION include)

```

前項でインストール時のインクルードパスを設定したため、生成される`mylib-config.cmake`には`${CMAKE_INSTALL_PREFIX}/include`をインクルードパスに加える設定が記されているでしょう。

##### （非ヘッダオンリーライブラリの場合）
このとき、もしライブラリがヘッダオンリーで無い場合は、以下のように必要に応じて生成されたライブラリのインストール先等も指定します。（パスには`${CMAKE_INSTALL_PREFIX}/`が付与されます。）

```cmake
install(TARGETS mylib2
	EXPORT mylib2-config
	# 静的ライブラリのインストール先
	ARCHIVE DESTINATION lib
	# 共有ライブラリのインストール先
	LIBRARY DESTINATION lib)
```

#### (3)configのインストール
前項までのコードからconfigファイルが生成されました。これを`find_package`によって探索されるディレクトリに配置しなければなりませんので、以下のようにします。

```cmake:mylib/CMakeLists.txt
# ++
	# インストールするconfigの指定、前項で生成したもの
install(EXPORT mylib-config
	# 名前空間の指定
	NAMESPACE mylib::
	# インストール先
	DESTINATION lib/cmake/mylib)
```

これで`make install`によって、`${CMAKE_INSTALL_PREFIX}/lib/cmake/mylib`に`mylib-config.cmake`が配置されるようになります。[^ulocal]

[^ulocal]: デフォルトでは`/usr/local/...`に配置される事になります。`find_package`の`$PATH`に含まれるディレクトリの末尾の`/bin`,`/sbin`を取り除いたディレクトリを探索するというルールに従って見つかります。

`NAMESPACE`は以下のように、他のパッケージからmylib内のターゲットを参照する際のプレフィックスになります。`<package>::`とするのがメジャーなようです。

```cmake:another/CMakeLists.txt
target_link_libraries(your-exe mylib::mylib)
```

#### (4)ヘッダファイルのインストール
インクルードパスに`${CMAKE_INSTALL_PREFIX}/include`を指定しましたが、実際にそこにヘッダファイルを配置するのは別の設定で行わないといけません。[^phdr]以下のようにしてディレクトリの中身をコピーします。

[^phdr]: `install(TARGETS ...)`には`PUBLIC_HEADER DESTINATION`なるパラメータがありますが、これは`target_include_directories`の指定とは無関係です。[cmake-properties(7) » PUBLIC_HEADER](https://cmake.org/cmake/help/latest/prop_tgt/PUBLIC_HEADER.html#prop_tgt:PUBLIC_HEADER)

```cmake:mylib/CMakeLists.txt
# ++
	# コピー元、末尾の`/`は要注意
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
	# コピー先
	DESTINATION include)
```

注意が必要なのは`DIRECTORY`の指定の末尾の`/`で、この有無によって結果が変わるようです。`DIRECTORY`に`dir/`と指定した場合は`dir/`の中身のファイルやディレクトリが全て`DESTINATION`の下にコピーされますが、一方`dir`と指定した場合は、中身ではなく`dir`自体が`DESTINATION`の下にコピーされます。

尚、`install(TARGETS ...)`で指定した`ARCHIVE DESTINATION`や`LIBRARY DESTINATION`はこのような事をせずとも配置まで自動的に行われます。

#### (5)`NAMESPACE`に関するイディオム
大方の作業は以上で完了ですが、使い勝手の為にもうひと手間かける事ができます。
さて、ここで定義した`mylib`ターゲットを外部のCMakeプロジェクトから利用する際、
`add_subdirectory(mylib)`を使用した場合はもちろん`mylib`として、`find_package(mylib)`を使用した場合は名前空間のプレフィックスが付いて`mylib::mylib`として参照できる事になります。
これから紹介するのは、どちらの場合でも`mylib::mylib`の名前で統一的に参照するためのイディオムです。


方法は至って単純で、`mylib`のエイリアスとして`mylib::mylib`を作成します。

```cmake:mylib/CMakeLists.txt
add_library(mylib INTERFACE)

# ++
add_library(mylib::mylib ALIAS mylib)
```

これで`add_subdirectory`を用いた場合にも、`mylib::mylib`の名前で`mylib`を参照できるようになりました。

### バージョンの互換性の設定
大方の作業は以上で完了ですが、使い勝手の為に(ry
`find_package`では第2パラメータで、要求するパッケージのバージョンを指定する事ができます。

```cmake:another/CMakeLists.txt
find_package(mylib 0.1.0)
```

パッケージのバージョンと要求されたバージョンとの互換性を判定するファイル[^vfile]、`<package-name>-config-version.cmake`あるいは`<PackageName>ConfigVersion.cmake`をインストールに含める事で、この指定に意味を持たせられます。[^vspec]

[^vfile]: Package Version Fileと呼ぶ。[cmake-packages(7)](https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file)

[^vspec]: Package Version Fileが無い状態でバージョンが要求されると`find_package`は失敗します。


典型的なPackage Version Fileは、`CMakePackageConfigHelpers`の`write_basic_package_version_file`というユーティリティを用いて簡単に生成できます。

```cmake:mylib/CMakeLists.txt
# ++
include(CMakePackageConfigHelpers)

# Package Version Fileの生成
write_basic_package_version_file(
	# 生成するファイルの名前
	${CMAKE_CURRENT_BINARY_DIR}/mylib-config-version.cmake
	# 互換性の判定方法の指定
	COMPATIBILITY SameMajorVersion)
	# バージョンの指定、project(...)で指定しているため省略可能
#	VERSION 0.1)

# Package Version Fileのインストール
	# コピー元
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mylib-config-version.cmake
	# コピー先
	DESTINATION lib/cmake/mylib)
```

これで`make install`時に、`${CMAKE_INSTALL_PREFIX}/lib/cmake/mylib/mylib-config-version.cmake`が作られるようになります。`write_basic_package_version_file`で指定できる`COMPATIBILITY`は`SameMajorVersion`、`AnyNewerVersion`、`ExactVersion`の3種類です。

`SameMajorVersion`では実在するバージョンが要求するバージョンより新しく（あるいは等しく）、かつSemVerの1桁目が等しい場合に互換性があると判断されます。[^semv]雑に例を挙げます。

[^semv]: 1桁目が0の時だけ特殊な処理が行われるというような事は無いようです。

|要求ver|実在ver|互換性|
|---:|---:|:--:|
|`0.1.0`|`0.1`|OK|
|`0.1.0`|`0.3`|OK|
|`0.1`|`0.1.0`|OK|
|`0.1`|`0.1.5`|OK|
|`0.1.5`|`0.1`|NG|
|`1.1`|`1.3.0`|OK|
|`1.1.0`|`1.3.0`|OK|
|`1.5.0`|`1.3.0`|NG|
|`0.3.0`|`1.3.0`|NG|

## まとめ
今回書いた`CMakeLists.txt`の全容は以下の通りです。

```cmake
cmake_minimum_required(VERSION 3.8)

project(mylib VERSION 0.1.0 LANGUAGES CXX)

add_library(mylib INTERFACE)

add_library(mylib::mylib ALIAS mylib)

target_compile_features(mylib INTERFACE cxx_std_11)

target_include_directories(mylib INTERFACE
	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
	$<INSTALL_INTERFACE:include>)

install(TARGETS mylib
	EXPORT mylib-config)

include(CMakePackageConfigHelpers)

write_basic_package_version_file(
	${CMAKE_CURRENT_BINARY_DIR}/mylib-config-version.cmake
	COMPATIBILITY SameMajorVersion)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mylib-config-version.cmake
	DESTINATION lib/cmake/mylib)

install(EXPORT mylib-config
	NAMESPACE mylib::
	DESTINATION lib/cmake/mylib)

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
	DESTINATION include)
```

これによって、作成したライブラリをシステムにインストールできるようになりました。

```console
~ $ cd mylib/build
mylib/build $ cmake ..
mylib/build $ sudo make install
```

`mylib`は他のプロジェクトの`CMakeLists.txt`からは以下のように利用できます。

```cmake
find_package(mylib REQUIRED)

add_executable(your-exe main.cpp)

target_link_libraries(your-exe mylib::mylib)
```

デフォルトでは`/usr/local/...`にインストールされるのでCMake管理外のコードからも容易に利用できるはずです。（ライブラリを生成した場合も`-l`オプション等でリンクできるでしょう。）

```console
~ $ cat main.cpp
#include <mylib/hello_world.hpp>

int main() {
  mylib::hello_world();
}
~ $ g++ main.cpp
```

インストールされたファイル群のパスは`${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt`に記録されます。

----
参考
- Installing - Modern CMake - https://cliutils.gitlab.io/modern-cmake/chapters/install/installing.html
- CMakeスクリプトを作成する際のガイドライン - Qiita - https://qiita.com/shohirose/items/5b406f060cd5557814e9
- hana/CMakeLists.txt - boostorg/hana - https://github.com/boostorg/hana/blob/master/CMakeLists.txt
- Modern CMake for Library Developers - unclejimbo's site - https://unclejimbo.github.io/2018/06/08/Modern-CMake-for-Library-Developers/
- Effective CMake - C++Now 2017 - https://github.com/boostcon/cppnow_presentations_2017/blob/master/05-19-2017_friday/effective_cmake__daniel_pfeifer__cppnow_05-19-2017.pdf
- It's Time To Do CMake Right - Pablo Arias - https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
- find_packageの動作 - Qiita - https://qiita.com/osamu0329/items/bd3d1e50edf37c277fa9