こんにちは。
ここ北部九州は梅雨入りがずいぶん遅れたと思ったら土砂降り、そして晴れ。気がついたら七夕です。光陰矢の如しを実感する今日このごろです。
さて、今回は比較的よく使うCMakeコマンドの一つ find_package について解説します。これは他のライブラリを使う場合の各種設定情報(インクルード・パスやライブラリ・パス等)の取り出しを自動化するコマンドです。ちょっと癖がありますが仕組みを悟れば難しいものではありませんので、頑張って解説してみます。
1.find_packegeの基本的な仕組み
find_package(使いたいパッケージ名 [オプション])のような形で使いますが、その本質は「使いたいパッケージ(≒ライブラリ)名」に対応する cmake スクリプトを呼び出し、そのスクリプトが必要な情報(インクルード・パス等)の取り出しを行います。
以下、その具体的な仕組みの概要を解説します。
なお、「使いたいパッケージ名に対応する cmake スクリプト」では長いので、以下「find_package スクリプト」と呼びます。
1-1. 2つの動作モード
find_package()コマンドは大きく2つのモードがあります。
モジュール・モードとコンフィグ・モードです。
この2つはデフォルトでは次のように自動的に切り替わります。
- モジュール・モードで「find_package スクリプト」を探し、
- 見つからなかったらコンフィグ・モードで探します。
- そして、見つかった「find_package スクリプト」を実行します。
また、それぞれを明示的に指定することもできます。
なお、これらのオプションの具体的な指定方法については公式のマニュアルを参照下さい。
ここでは重要事項についてのみ解説します。
モジュール・モードとコンフィグ・モードの振る舞いの相違
振る舞いの相違は上述したように cmake スクリプトの探し方の相違がほとんです。見つかった後はどちらの場合も同様に実行されます。この辺りを詳しく解説されている記事がありました。find_package で使用するcmake スクリプトを開発する人にとって参考になると思います。
そして、実は振る舞い以外に「位置付け」が大きく異なります。位置付けについては後述します。
1-2. モジュール・モード
公式のマニュアルでは、「基本的なシグネチャ(コマンドの基本オプション)とモジュール・モード」として説明されています。
基本的なシグニチャの範囲内で指定することが推奨されています。そのように使った時は、MODULEオプションを指定していなければ1-1.で述べたように振る舞います。もし、MODULEオプションを指定しているとコンフィグ・モードに切り替わりません。
さて、モジュール・モードで探す「find_package スクリプト」の名前は、次の通りです。
- Find<使いたいパッケージ名>.cmake
検索するフォルダは次の通りです。
- まず、CMAKE_MODULE_PATH で指定したフォルダ
- 次に、cmakeインストール時にインストールされるcmakeスクリプト群のフォルダ
Windowsでは <CMakeインストール先>\share\cmake-<CMakeバージョン>\Modules になるようです。
手元のubuntu16.04では /usr/share/cmake-3.5/Modules に入ってました。
標準でインストールされるモジュールに不都合がある時、修正したモジュールをCMAKE_MODULE_PATHに放り込んで使うと便利ということのようです。
モジュール・モードの位置付け
モジュール・モードの cmake スクリプトは、CMakeのインストール時にインストールされます。つまり、CMakeに含まれるモジュールという位置付けです。例えば、boostライブラリを使うためのモジュール FindBoost.cmake はCMakeインストール時に上記フォルダへインストールされます。また、CMake の公式ドキュメントに使い方が書かれています。
肝心のパッケージ(=ライブラリ)の在り処の指定方法
一部の「find_package スクリプト」はモジュール・モードの時に「使いたいパッケージ(=ライブラリ)そのもの」を探すフォルダを指定できる場合があります。
FindBoost.cmake は、BOOST_ROOT変数で指定できます。
パッケージの在り処の指定について
私はWindows上の開発経験が長いため、linuxのようにリンクするライブラリをツールチェインが自動的に探すという環境に慣れていないせいもあるとは思いますが、リンクするライブラリの在り処を指定できないというのは正直つらいです。
以前、Macでアプリを開発している時に発生した問題があります。そのアプリは、システムに標準でインストールされている あるライブラリに依存していました。ある時、開発マシンに何かのソフトをインストールするとそのライブラリの最新版が異なるパスへインストールされました。ツールチェインが自動的に最新版とリンクしてしまい、当該アプリがシステム標準とは異なる場所にあるライブラリに依存してしまいました。するとそのアプリを配布したマシンでアプリが起動しないわけです。
リンクするライブラリを自動で探すことの弊害と思います。まるで動的型付け言語を使っているような苦労です。
1-3. コンフィグ・モード
公式のマニュアルでは、「全シグネチャ(コマンドの全てのオプション)とコンフィグ・モード」として説明されています。
基本的なシグニチャから外れて使うと、モジュール・モードをすっ飛ばしてコンフィグ・モードで振る舞います。CONFIGやNO_MODULEオプションを明示的に与えることもこれに含まれます。ですが、この使い方をする必要に迫られることはあまりないと思います。コンフィグ・モードの振る舞い方だけ把握していれば、多くの場合十分と思います。
さて、コンフィグ・モードで探す「find_package スクリプト」の名前は、次の通りです。
- <使いたいパッケージ名>Config.cmake
- <使いたいパッケージ名を小文字にした文字列>-config.cmake
検索するフォルダを明示的に指定します。
CMakeが該当のライブラリ用の「find_package スクリプト」を提供していない時、ライブラリ側で「find_package スクリプト」を提供していることがあります。その時、ライブラリのインストール先のどこかに「*Config.cmake
」や「*-config.cmake
」ファイルがありますのでそれを探してみて下さい。(見つからない時は、CMakeでfind_packageできないので必要な設定を手動で行えば良いです。)
そのフォルダが見つかったら、<パッケージ名>_DIR変数にそのフォルダのパスを設定してから、find_package()を呼び出せば見つけてくれます。
開発者毎に依存ライブラリのインストール先は異なるケースが多いので、CMakeでコンフィグする時にパラメータで与えることが一般的です。
01 02 | > cmake .. -D<使いたいパッケージ名>_DIR=<当該find_packageスクリプトがあるフォルダのパス> 例> cmake .. -DOpenCV_DIR=C:\opencv\build |
なお、実際に検索するフォルダは上述したQiitaの記事にて解説されています。
正直、複雑な使い方を覚えられないので、単純に「<パッケージ名>_DIRで *Config.cmake
の在り処を指定する」と覚えておけば良いのではないかと思います。
コンフィグ・モードの位置付け
コンフィグ・モードの cmake スクリプトは、使いたいパッケージのインストール時にインストールされます。つまり、CMakeではなくパッケージ側が提供するという位置づけです。例えば、OpenCVをインストールすると OpenCVConfig.cmake がインストールされます。OpenCV_DIRにこのファイルがあるフォルダのパスを指定することでfind_packageがOpenCVをリンクするために必要な情報を取り出してくれます。
2.使用例
比較的使われることが多いと思われる boost(モジュール・モード) と OpenCV(コンフィグ・モード) を例にして使い方の例を示してみようと思います。(boostやOpenCVそのもののインストール方法や使い方まで解説し始めると正直キリがないので典型例だけとなりますが、ご容赦下さい。)
2-1. boostをCMakeプロジェクトで使用する例
boostはboost側ではCMakeに対応していません。しかし、CMakeの方でboostをサポートしています。
つまり、boostはFindBoost.cmakeによるモジュール・モードで使うことになります。
FindBoost.cmakeは、FindBoost.cmakeが知っているboostのみに対応しているようで、内部で明示的に対応するboostのバージョンを列挙しています。FindBoost.cmakeの1170行付近で_Boost_KNOWN_VERSIONS変数にセットしています。
ですので、boostを使う時は新しいCMakeを使わないと find_packageに失敗する場合があります。
ただ、今回の記事を書いていて気がついたのですが、boostは1.66.0以降でライブラリのファイル名を変更したようです。
残念ながら、FindBoost.cmakeは その変更に対応していないようで、少なくとも私の環境(Visual C++ 2017)ではboost 1.66.0以降に対して、find_packageがうまく動作しませんでした。ですので find_packageが動作した boost 1.65.1 を使って例を示します。
ざっくりの手順は以下の通りです。
- boostライブラリをダウンロードし、解凍する
- boostライブラリをビルドする
- boostを使用するサンプルソースを準備する
- boostライブラリをサンプルソースとリンクするCMakeLists.txtを用意する
- コンフィグ→ビルド→実行
Windows 10とVisual C++ 2017を用いて説明します。Visual C++ 2019でも恐らく大差ないだろうと思います。(まだVC++ 2019をインストールしていないので未確認です。ごめんなさい。)
linuxの方は適宜読み替えて下さい。linuxはバイナリ・パッケージがディストリビュータから提供されていることが多いようです。
boostのライブラリ・ファイル名変更
実は以前からboostのビルド・システムには問題があり、32ビットのライブラリと64ビットのライブラリのファイル名が同じでした。なので、32bitビルドしたものと64bitビルドしたもののインストール先は別のフォルダを指定する必要がありました。その結果boostの多くを占めるヘッダ・ファイルを2重にインストールする必要があり、あまり気持ちの良いものではなかったです。
64bitビルドしたものの新しいファイル名には”x64″が追加されていますので、この問題に対処したのではないかと思います。
2-1-1. boostライブラリをダウンロードし、解凍する
boostの古いバージョンは ここ からダウンロードできます。
今回は、1.65.1を使いますので、この辺にあります。
Windowsでは、boost_1_65_1.7z をダウンロードすることがお薦めです。7-zipをインストールされていない方は boost_1_65_1.zip をダウンロードして下さい。
そして、お好みのフォルダへ解凍して下さい。
なお、.zipの方はあまり深いフォルダで解凍しようとするとパス名が長すぎることが原因と思いますが、解凍に失敗することがあるようです。ダウンロードをやり直しても失敗するようでしたら浅いフォルダで解凍してみて下さい。
2-1-2. boostライブラリをビルドする
boostのビルド方法は公式に解説があります。が、量が多くて読み下すのはたいへんそうです。
日本語のサイトの解説は比較的まとまっていると思います。下記の例で与えているオプションの意味はここで調べると良いです。
- Visual Studioのコマンド・プロンプトを起動して下さい。
64ビット・ビルドする場合は「VS 2017 用 x64 Native Tools コマンド プロンプト」でOKです。 - boostを解凍したフォルダ(bootstrap.batがあるフォルダです)へ cd して下さい。
- bootstrap.batを実行して下さい。
これでboostをビルドするためのサブツール b2.exe がビルドされます。 - boostをビルドし、インストールします。
> b2 toolset=msvc link=static,shared address-model=64 install -j8 –prefix=C:\Boost64
これで静的リンク・ライブラリと動的リンク・ライブラリ(dll)を64bitsビルドし、C:\Boost64
フォルダへインストールします。(インストール先はご自身の環境に合わせて変更下さい。)
ところで、runtime-linkの指定方法については、以前C++形式の動的リンク・ライブラリの書き方(msvc編)で考え方を解説したことがあります。一見無関係のようにも見えますが、boostについて記載していますので、よかったら参考にされて下さい。
2-1-3. boostを使用するサンプルソースを準備する
何を使ってもよいのですが、boost公式にあるサンプルソース群の内、C++標準ライブラリのサポートが貧弱なので使いたいことが多そうなboost:date_timeのサンプルから日付の差分のサンプル(Time Math)を使ってみます。
2-1-4. boostライブラリをサンプルソースとリンクするCMakeLists.txtを用意する
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | cmake_minimum_required(VERSION 3.14.5) project(sample) if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11") endif() message(STATUS "BOOST_ROOT=${BOOST_ROOT}") set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) find_package(Boost REQUIRED COMPONENTS date_time) message(STATUS " Boost_INCLUDE_DIR=${Boost_INCLUDE_DIR}") message(STATUS " Boost_LIBRARY_DIR=${Boost_LIBRARY_DIR}") message(STATUS " Boost_LIBRARIES =${Boost_LIBRARIES}") include_directories("${Boost_INCLUDE_DIRS}") link_directories("${Boost_LIBRARY_DIR}") add_executable(sample sample.cpp) target_link_libraries(sample ${Boost_LIBRARIES}) |
重要な行は14行目のfind_package()です。
ここで以下を指定しています。
- boostを探すこと
- REQUIRED を指定することでBoostが見つからなかったらCMakeコンフィグに失敗するようになります。
- COMPONENTS にて、使用するコンポーネントを指定しています。
date_timeを使うのでこれを指定しています。
FindBoost.cmakeの場合は COMPONENTS を指定しないとリンクしないことになります。
01行:cmake_minimum_requiredにて外陣で最新のCMakeを指定しています。
10行:cmake生成コマンドでBOOST_ROOTを与えますので確認のため表示しています。
11行:boostをスタティック・リンクするよう指示します。
12行:マルチスレッド版を指定しています。(今回は意味ないですが、一般的には指定した方が良いです)
16~18行:find_packageが返却する重要変数を表示します。
20行:インクルード・パスを指定しています。
21行:ライブラリ・パスを指定しています。
23行:リンクするライブラリを指定しています。
FindBoost.cmakeは、VC++用にはBoost_LIBRARY_DIRを返却していないようです。
2-1-5. コンフィグ→ビルド→実行
以下のコマンドで、CMakeコンフィグ、VC++でビルドし、実行します。
3行目のCMakeコンフィグコマンドの”-DBOOST_ROOT=C:\Boost64″オプションにてBOOST_ROOT変数へboostのインストール先を設定しています。
01 02 03 04 05 | mkdir build cd build cmake -G "Visual Studio 15 2017 Win64" .. -DBOOST_ROOT=C:\Boost64 cmake --build . --config Release Release\sample.exe |
2-2. OpenCVをCMakeプロジェクトで使用する例
OpenCVはCMake側ではOpenCVに対応していません。しかし、逆にOpenCVの方でCMakeをサポートしています。つまり、OpenCVはOpenCVConfig.cmakeによるコンフィグ・モードで使うことになります。
ざっくりの手順は以下の通りです。
- OpenCVライブラリのプリビルド版をダウンロードし、解凍する
- OpenCVを使用するサンプルソースを準備する
- CMakeLists.txtでOpenCVライブラリをサンプルソースとリンクする
- コンフィグ→ビルド→OpenCVのdllのコピー→実行
OpenCVはboostと違ってWindowsでプリビルド版が提供されていますので、それを用います。
boostと同様にWindows 10とVisual C++ 2017を用いて説明します。
linuxの方は適宜読み替えて下さい。しかし、どうもlinux用バイナリは提供されていないようでビルドが必要そうです。ビルド方法の記事がありました。ちょっとたいへんそうです。例としてはあまり良くなかったですね。申し訳ないです。
2-2-1. OpenCVライブラリをダウンロードし、解凍する
OpenCVは ここ からダウンロードできます。
今回は、現時点での最新版の4.1.0のWindows版を使いますので、これをダウンロードして下さい。
ダウンロードした opencv-4.1.0-vc14_vc15.exe を実行すると解凍先を聞いてきますので、適宜指定して下さい。C:\を指定するとC:\opencvへ解凍されます。以下、C:\を指定したものとして例を示します。
OpenCVの「find_package スクリプト」のファイル名はOpenCVConfig.cmakeで、インストール先のbuildフォルダの下にあります。上記の位置へOpenCV 4.1をインストールした場合 C:\opencv\build にありました。
2-2-2. OpenCVを使用するサンプルソースを準備する
OpenCVについてくるサンプルのなかから C:\opencv\sources\samples\cpp\contours2.cpp を使ってみました。
2-2-3. OpenCVライブラリをサンプルソースとリンクするCMakeLists.txtを用意する
ちょっと時間が無くなってきたので、これもOpenCV付属のCMakeLists.txtを修正して使いました。
C:\opencv\sources\samples\cpp\example_cmake\CMakeLists.txt です。これの27行目の example.cpp を contours2.cpp へ変更して下さい。
ここにあるサンプルソース example.cpp を使っても良かったのですが、どうもカメラが必要そうなので見送りました。WEBカメラなどをお持ちの方は example.cpp をトライしてみるのも良いと思います。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # cmake needs this line cmake_minimum_required(VERSION 3.1) # Enable C++11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Define project name project(opencv_example_project) # Find OpenCV, you may need to set OpenCV_DIR variable # to the absolute path to the directory containing OpenCVConfig.cmake file # via the command line or GUI find_package(OpenCV REQUIRED) # If the package has been found, several variables will # be set, you can find the full list with descriptions # in the OpenCVConfig.cmake file. # Print some message showing some of them message(STATUS "OpenCV library status:") message(STATUS " config: ${OpenCV_DIR}") message(STATUS " version: ${OpenCV_VERSION}") message(STATUS " libraries: ${OpenCV_LIBS}") message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}") # Declare the executable target built from your sources add_executable(opencv_example contours2.cpp) # Link your application with OpenCV libraries target_link_libraries(opencv_example ${OpenCV_LIBS}) |
重要な行は14行目のfind_package()です。
ここで以下を指定しています。
- OpenCVを探すこと
- REQUIRED を指定することでOpenCVが見つからなかったらCMakeコンフィグに失敗するようになります。
COMPONENTS を指定する必要はないようです。最近のOpenCVのライブラリ・ファイルは1つしかないからだと思います。
2-2-4. コンフィグ→ビルド→OpenCVのdllのコピー→実行
OpenCVのプリビルド版はdllのみ付属していますので、実行する際にはdllのあるところにパスを切るか、exeのあるところにdllをコピーする必要があります。
ここでは確実に動作させるため、exeのあるところへコピーすることにしました。
以下のコマンドで、CMakeコンフィグ、VC++でビルドし、dllをコピーして実行します。
3行目のcmakeコンフィグ・コマンドの”-DOpenCV_DIR=C:\opencv\build”オプションにてOpenCV_DIR変数へOpenCVのOpenCVConfig.cmakeスクリプトのあるフォルダを設定しています。
01 02 03 04 05 06 07 | mkdir build cd build cmake -G "Visual Studio 15 2017 Win64" .. -DOpenCV_DIR=C:\opencv\build cmake --build . --config Release copy C:\opencv\build\x64\vc15\bin\opencv_world410.dll Release Release\opencv_example.exe |
dllのコピーもCMakeで自動化できるのですが、それはまた後日解説しようと思います。
3.まとめ
最後の方はちょっと駆け足になってしまいましたが、CMakeのfind_packageの振る舞いについて重要なモジュール・モードとコンフィグ・モードについて解説しました。使用したいライブラリがどちらに該当するのか調べた上でfind_packageすれば比較的スムーズに使えると思います。
- モジュール・モードの場合は公式にドキュメントがありますので、公式に記載があればモジュール・モードです。使い方も載っています。
- 公式にドキュメントがない場合は、ライブラリのドキュメントを確認しましょう。
対応していればCMakeを使った使い方の説明が有る筈です。この場合はコンフィグ・モードで使えます。
また、インストール先に*.cmake
がないか探してみるのも手です。もし、*Config.cmake
、もしくは、*-config.cmake
があればコンフィグ・モードに対応している筈ですので、それらが見つかったフォルダを指定してcmakeコンフィグすると良いです。使い方がそれらのcmakeスクリプト・ファイルにかかれている可能性が高いのでそれを見てみましょう。
それでは今回はここで終わります。お疲れ様でした!
いつも勉強させて頂いております。
Windows 10+Visual Studio 2017+CMake3.17.2環境で、Boostを試してみたところ、上記の方法で、ビルドしたboostライブラリでは、うまくパッケージを見つけることができませんでした。私がfind_packageの使い方をよく理解していないことに原因があるかと思いますが、
https://sourceforge.net/projects/boost/files/boost-binaries/1.65.1/
で、boost_1_65_1-msvc-14.1-64.exeをインストールしたところサンプルをビルドすることができました。同様の方法だと、boost_1_70_0でも問題なくビルドできました。
申し訳ございません。先程のコメントにおいて、パッケージを見つけられなかったのは、「2-1-5. コンフィグ→ビルド→実行」を読んでいなかったことが原因でした。「-DBOOST_ROOT=C:\Boost64」オブションを見逃していました。このオプション付ければビルドしたBoostでも問題なくサンプルをビルドできました。先程のコメントは、削除してください。