OpenCVで使われてるCMakeレシピ


# はじめに
- これは、OpenCV Advent Calendar 2018 ~~19日目の記事です。~~12日目の記事です。空いてるのが寂しかったので前倒しで公開することにしました。
- 関連記事は[目次](http://qiita.com/advent-calendar/2018/opencv)にまとめられています。
- なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。
- 先日、こんなことがありました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">文脈全く理解してないですけど、config.h みたいなのを作るのならば add_custom_command じゃなくて configure_file がおすすめですよ。 <a href="https://t.co/YWYGu9h5NT">https://t.co/YWYGu9h5NT</a></p>&mdash; Tomoaki Teshima (@tomoaki_teshima) <a href="https://twitter.com/tomoaki_teshima/status/1047054639114399745?ref_src=twsrc%5Etfw">2018年10月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# TL;DR
- OpenCVはビルド時にCMakeが使われるわけですが、様々なプラットフォームでトラブルなくビルドするためにあんな技やこんな技が使われていますよ、というお話。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">cmakeのドキュメントとしてのOpenCVの可能性を感じる。</p>&mdash; 熊樹もしくはkumagi (@kumagi) <a href="https://twitter.com/kumagi/status/1047055748700737537?ref_src=twsrc%5Etfw">2018年10月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- @kumagi先生のおっしゃるように、OpenCVを読み解くとCMakeの使い方がわかる、って気もします。


# cvconfig.h
 - 本題のkumagi先生が言っていた、config.hというのは、要は下記みたいなヘッダのことだと思っています。

```config.h
#define HAVE_CAMERA_LIBRARY
#define HAVE_MATRIX_LIBRARY
#define HAVE_ETHER_GRAB_LIBRARY
//#define HAVE_EIGEN
```
 - このような```config.h```があったとして、全てのソースコードからこいつを```include```すれば、特定の機能をON/OFFすることが容易になります。
 - 私も学生時代、個人でソースコードを管理してましたが、当時の所属研究室ではマシンは共用で、日によって席を変えてビルドすることもあり、そうすると
    - 「このマシンにはこのライブラリが入ってない」
    - 「このマシンにはこのグラバのドライバが入ってない」
 - などの細かい差異でビルドができたりできなかったりしました。
 - なので前述の```config.h```みたいなのを用意してビルドできない問題を回避したりしてました。
 - このアプローチ自体はよくある話だと思いますし、この記事を読んでる方も「あるある」と頷いている人が多いのではないでしょうか。
 - 問題は、この```config.h```をどうやってメンテナンスするのか、ということです。
 - 前述の```config.h```は割と短いので、メンテナンスは大したこと無いですが、OpenCVみたいなライブラリを、どこでもオートマチックにビルドするためには、メンテナンスが大変です。

# そこでCMakeですよ
 - CMakeには、このようなファイルを自動生成するための、```configure_file```コマンドがあります。

 - [configure_file &#8212; CMake 3.0.2 Documentation](https://cmake.org/cmake/help/v3.0/command/configure_file.html)

 - このコマンドを使うと、CMakeの変数をC/C++の形に編集してくれるのです。

 - 実際の例を見てみましょう。OpenCVでは、以下のような```cvconfig.h```があります。当然中身は環境によって異なります。

```cpp:cvconfig.h
#ifndef OPENCV_CVCONFIG_H_INCLUDED
#define OPENCV_CVCONFIG_H_INCLUDED

/* OpenCV compiled as static or dynamic libs */
#define BUILD_SHARED_LIBS

/* OpenCV intrinsics optimized code */
#define CV_ENABLE_INTRINSICS

/* OpenCV additional optimized code */
/* #undef CV_DISABLE_OPTIMIZATION */

/* Compile for 'real' NVIDIA GPU architectures */
#define CUDA_ARCH_BIN " 61"

/* OpenCL Support */
#define HAVE_OPENCL
/* #undef HAVE_OPENCL_STATIC */
/* #undef HAVE_OPENCL_SVM */

/* OpenEXR codec */
#define HAVE_OPENEXR
```

 - 当たり前ですが、このファイルはバージョン管理されていません。ですので、リポジトリには```cvconfig.h```は存在しません。
 - その代り、cmake実行完了時点で自動生成されます。
 - ```cvconfig.h```は存在しないと書きましたが、自動生成される前の「ファイルのもと」は存在していて、バージョン管理もされています。 [cmake/templates/cvconfig.h.in (OpenCV 4.0.0版) ](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/cmake/templates/cvconfig.h.in)がそうです。
 - ちょっと覗いてみましょう。

```cpp:cvconfig.h.in
#ifndef OPENCV_CVCONFIG_H_INCLUDED
#define OPENCV_CVCONFIG_H_INCLUDED

/* OpenCV compiled as static or dynamic libs */
#cmakedefine BUILD_SHARED_LIBS

/* OpenCV intrinsics optimized code */
#cmakedefine CV_ENABLE_INTRINSICS

/* OpenCV additional optimized code */
#cmakedefine CV_DISABLE_OPTIMIZATION

/* Compile for 'real' NVIDIA GPU architectures */
#define CUDA_ARCH_BIN "${OPENCV_CUDA_ARCH_BIN}"

/* OpenCL Support */
#cmakedefine HAVE_OPENCL
#cmakedefine HAVE_OPENCL_STATIC
#cmakedefine HAVE_OPENCL_SVM

/* OpenEXR codec */
#cmakedefine HAVE_OPENEXR
```

 - 先程の```cvconfig.h```と見比べると、似ていますが、微妙に違います。```#cmakedefine``` という、一見```define```に見えるコマンドがずらりと並んでいます。そして、その右側にあるのはCMakeでも使われている変数があります。
 - このファイルを生成しているのは[ここ](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/cmake/OpenCVGenHeaders.cmake#L2-L3)です。

```OpenCVGenHeaders.cmake
# platform-specific config file
configure_file("${OpenCV_SOURCE_DIR}/cmake/templates/cvconfig.h.in" "${OPENCV_CONFIG_FILE_INCLUDE_DIR}/cvconfig.h")
configure_file("${OpenCV_SOURCE_DIR}/cmake/templates/cvconfig.h.in" "${OPENCV_CONFIG_FILE_INCLUDE_DIR}/opencv2/cvconfig.h")
```

 - このファイルでは、```configure_file```コマンドを呼び、その引数として```cvconfig.h.in```(先程見た「ファイルのもと」)と、```config.h```の出力先の2つを指定しています。
 - こうすることで、CMake内の変数をもとに、```define```が有効になったり無効になったファイルができあがる訳です。
 - これにより、```cvconfig.h```を管理しないでも、自動生成できます。かなり便利な機能ですので是非皆様も使ってみて下さい。

```
config.h を自作するなら、CMakeのconfigure_fileコマンドを使おう
```

# inline と \_\_inline__ と \_\_inline
https://github.com/opencv/opencv/blob/master/3rdparty/libtiff/CMakeLists.txt#L39
```foreach(inline_keyword "inline" "__inline__" "__inline")```

 - C++の予約語の一つ```inline```関数はご存知でしょうか。関数呼び出しでなく、関数自体は呼び元に埋め込むことで、主に実行速度の高速化に寄与するためのオプションです。
 - 実際にインライン展開（埋め込み）が行われるかどうかはコンパイラが判断するのですが、そのコンパイラのキーワードが実は決まって無い問題があるのです。
 - 前述の通りC++では```inline```というキーワードが決まっていますが、古いCコンパイラの規約にはこのinlineは無いのです。[^1]。
 - で、コンパイラによってはこの```inline```キーワードを独自に対応していたりします。規約で決まっていないため、コンパイラによって、```inline```だったり```__inline__```だったり```__inline```だったりします。
 - こういうコンパイラ依存の機能を使う場合、よくある対処法はコンパイラごとに```ifdef```で区切るアプローチです

[^1]: C99から```inline```が規格に盛り込まれた模様


```sampleifdef.h
#if defined __GNUC__
#define FOO  Bar
#elif defined _MSC_VER
#define FOO  foo
#else
#define FOO
#endif
```

 - ```ifdef __GNUC___```みたいなプリプロセッサで切り分けることが行われます。が、OpenCVでは[^2]一味違ったアプローチが行われます。
 - OpenCV内の```3rdparty```以下にある```libtiff```内には[Cのソースコード](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/3rdparty/libtiff/tif_fax3.c)が含まれており、そこでは[```inline```関数が宣言されて](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/3rdparty/libtiff/tif_fax3.c#L843)います。
 - そして、その時の識別子は```inline```です。

[^2]: というか厳密には```libtiff```ライブラリ

```c:tif_fax3.c
inline static int32
find0span(unsigned char* bp, int32 bs, int32 be)
```

 - これだとGCCでは識別子が```inline```なので良いのですが、Visual Studio 2013とそれ以前では識別子が```__inline```のため、このままだとコンパイルエラーになる（はず）です。
 - **ところが実際にはなりません**。
 - 実際にはこのファイルからインクルードされる```tif_config.h```に以下のような記述があります。なお、この```tif_config.h```はCMake時に自動生成されるファイルです。

```tif_config.h
/* Define to `__inline__' or `__inline' if that's what the C compiler
   calls it, or to nothing if 'inline' is not supported under any name.  */
#ifndef __cplusplus
#define inline inline
#endif
```

 - では、Visual Studio 2013ではどうなるのか、実際に見てみましょう。

```tif_config.h
/* Define to `__inline__' or `__inline' if that's what the C compiler
   calls it, or to nothing if 'inline' is not supported under any name.  */
#ifndef __cplusplus
#define inline __inline
#endif
```

 - ここで、```define```を使うことで```inline```識別子が```__inline```識別子に置き換えられます。
 - どこでこの```define```が発生するのかと言うと、[```libtiff```内の```CMakeLists.txt```](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/3rdparty/libtiff/CMakeLists.txt)に[このからくりの本質](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/3rdparty/libtiff/CMakeLists.txt#L39-L56)があります。

```cmake:CMakeLists.txt
foreach(inline_keyword "inline" "__inline__" "__inline")
  if(NOT DEFINED C_INLINE)
    set(CMAKE_REQUIRED_DEFINITIONS_SAVE ${CMAKE_REQUIRED_DEFINITIONS})
    set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
        "-Dinline=${inline_keyword}")
    check_c_source_compiles("
        typedef int foo_t;
        static inline foo_t static_foo() {return 0;}
        foo_t foo(){return 0;}
        int main(int argc, char *argv[]) {return 0;}"
      C_HAS_${inline_keyword})
```

 - キーポイントは、1行目に列挙されてる```inline```, ```__inline__```, ```__inline```識別子と、[```check_c_source_compiles```コマンド](https://cmake.org/cmake/help/v3.0/module/CheckCSourceCompiles.html)です。
 - このコマンドは、ソースコードとオプションを渡し、実際にコンパイラでコンパイルした成否の結果を返す関数です。
 - これにより、CMake時点でコンパイラがどの識別子を使うのか、はたまた```inline```関数非対応なのかを知ることができます。
 - 前述の通り、コード自体はスッキリしたまま、ゴテゴテした```ifdef```を隠せるのは便利だと思います。

```
check_c_source_compiles を活用してifdefを減らそう！
```

# ビルド時ダウンロード
 - 状況は様々だと思いますが、「このバイナリはダウンロードして配置するだけ」なバイナリってよくあると思います。
 - プログラムに埋め込むアイコンだけだったら良いのですが、往々にしてこういうバイナリをリポジトリに追加すると雪だるま式にみんな追加し始めて手に負えなくなります。

```
Gitにバイナリ、ダメ絶対。
```

 - とは言え、手間を考えると、バイナリをリポジトリに追加したくなるのも人情です。[^3]
 - 手間を考えるとリポジトリにバイナリを追加したいし、リポジトリのサイズを考えるとこのバイナリはリポジトリに入れたくない。
 - こんな**ジレンマにハマったとき**は、個人的に**OpenCVでどう対処してるか**を参考にしています。
 - で、OpenCVでも実は```IPP```とかWindows用```ffmpeg```とか、バイナリをビルド時に利用しています。
 - では、このバイナリはどこから来てるのか？
 - 結論から言えば、OpenCVではバイナリ格納専用リポジトリを用意して、そこにはどんどんバイナリファイルがpushされています。そのリポジトリが[opencv_3rdparty](https://github.com/opencv/opencv_3rdparty)です。[^4]
 - しかし、皆さん```opencv_3rdparty```の存在をご存知なかったように、ここから手動でビルド済みバイナリをダウンロードされた方はまずいないと思います。
 - でも現にOpenCVではビルド時に```IPP```や```ffmpeg```などのビルド済みバイナリをダウンロードしてきます。では、どうやってダウンロードを実現しているのか？


[^3]: 余談ですが、私は数十KBのパワーポイントファイルとPDFをリポジトリに追加するのに猛反発して、結果昨年のOpenCVアドベントカレンダーに寄稿した「 [ビルドしただけでパワーポイントを生成するCMake](https://qiita.com/tomoaki_teshima/items/39bc1b6cca9f1cf8f040)」が誕生しました

[^4]: 厳密に言えば[opencv_extra](https://github.com/opencv/opencv_extra)リポジトリもバイナリがどんどんpushされてるので、バイナリ用のリポジトリが2つもあります。


```
はい、CMakeでやっています。
```

- 具体的には```cmake/OpenCVDownload.cmake```に記述があります。

```cmake
#  Download and optionally unpack a file
#
#  ocv_download(FILENAME p HASH h URL u1 [u2 ...] DESTINATION_DIR d [ID id] [STATUS s] [UNPACK] [RELATIVE_URL])
```

- この```ocv_download```関数内では```file```コマンドを呼んでいます

```cmake
    file(DOWNLOAD "${DL_URL}" "${CACHE_CANDIDATE}"
         INACTIVITY_TIMEOUT 60
         TIMEOUT 600
         STATUS status
         LOG __log)
```

- 具体的には、```file```コマンドの```DOWNLOAD```オプションを利用していて、これにはURL、ダウンロード用ディレクトリ、MD5ハッシュ値なんかを渡すと、

1. 指定URLから指定したディレクトリにダウンロード
2. MD5ハッシュ値を計算してバイナリを検証
3. ファイルを展開

 - ぐらいまで自動でやってくれます。
 - なおOpenCVでは後方互換[^5]のために、このあたりの機能は利用しておらず、自前で関数に組み込んでいます。しかし[3.0以降のCMakeでは上記のオプションは全てよしなに取り扱ってくれます](https://cmake.org/cmake/help/v3.0/command/file.html)
 - なお、似た機能ではgit submoduleがあります。
 - これはあるリポジトリに、**別のリポジトリの特定のコミット**を結びつける方法で、複数のオープンソースプロジェクトを連携させるときに使ったりします。が、正直**地獄を見る**と思います。
 - 個人的に```git submodule```は百害あって一利なしで、以下の[sakanazensenさん](https://twitter.com/sakanazensen)のツイートに私も強く同意します。

[^5]: OpenCV 4.0からCMakeの最低バージョンが[3.5.1に引き上げられました](https://github.com/opencv/opencv/blob/75ed282b20770a7a9b490102fd2e0b4fa26223e5/cmake/OpenCVMinDepVersions.cmake#L1)が、3.0系や2.4系では[2.8.12.2が最低バージョン](https://github.com/opencv/opencv/blob/3.4.4/cmake/OpenCVMinDepVersions.cmake#L1)であり、そのとき使えなかったコマンドが独自コマンドで提供されています。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">なぜ人はgit submoduleに魅力を感じてしまうのか。あんなにつらさしかないのに。モテるダメ男とそんなのばっか捕まえる女のようだ。</p>&mdash; 光の魚 (@sakanazensen) <a href="https://twitter.com/sakanazensen/status/966143191257722880?ref_src=twsrc%5Etfw">2018年2月21日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```
CMakeのFILEコマンドを使って便利に開発しましょう。
```
# dispatch
 - OpenCV 3.3から、dispatch機能が追加されました。特定の命令セットを使わないように、OpenCVのロード時（≠ビルド時）に指定できる機能で、古いプロセッサ用にAVX命令をdisableしてOpenCVをビルドし直し、みたいな悲劇がなくなりました。
 - このdispatchはかなりCMakeの芸術的な出来になっているので、このdispatch機能がどうやって成り立っているか、~~明日~~別記事で解説しようと思います。(日付変更に伴い文章変更)

# おわりに
 - OpenCVで使われていて、一般的にも使えそうなCMakeの便利機能を紹介しました。
 - [```configure_file```コマンド](https://cmake.org/cmake/help/v3.0/command/configure_file.html)
 - [```check_c_source_compiles```コマンド](https://cmake.org/cmake/help/v3.0/module/CheckCSourceCompiles.html)
 - [```file```コマンドの```DOWNLOAD```オプション](https://cmake.org/cmake/help/v3.0/command/file.html)
 - ~~明日も私の予定で、体力が残ってればdispatchがどう実現されているか解説します。~~