CMakeを使ってみた (7) find_packageとpkg_check_modulesによるライブラリ探索

久しぶりにCMakeの話。

外部の依存ライブラリがあるC/C++のコードをCMakeでビルドする場合、インクルードパスやライブラリパスを指定する必要がある。パスを直接指定する方法以前書いた。しかし、そこで書いたのはパスやライブラリ名を直接指定するもので、それらがすでに分かっている必要がある。

しかし、システムにインストールされたライブラリを使うような場合はそのパスを探し出して指定する必要がある。システムによってインストールされた場所が異なることがあるためだ。Unix系OSでよく使われるGNU Autotools環境では、ライブラリの探索はAutoconfの役目で、`./configure && make`の`./configure`スクリプトが行う。では同様のことをCMakeでやるにはどうすれば良いだろうか。

ということで今回はCMakeのライブラリ探索の話。方法はいくつかあるので、順番に見ていこう。

find_packageコマンドを使う

CMakeをインストールするとcmakeコマンドだけでなくたくさんのモジュールをインストールしており、よく知られたライブラリの探索モジュールも含まれている。利用可能なモジュールの一覧は`cmake --help-module-list`で表示できる。多数のモジュールが表示されるが、その中でFindXXXという名前になっているのが探索用のモジュールだ。

ここにあるライブラリであればfind_packageコマンドを使って探すことができる。まずはこれを使ってみよう。

例としてGTK2を使うアプリを考える。以下のような、GTK2を使うソースファイル (main.c) を作った。GTK自体の実験ではないので単にgtk_init()を呼び出すだけで何もしないプログラムだが、ビルドにはインクルードパスなどの指定が必要だ。

#include <gtk/gtk.h>

int main(int argc, char **argv){
    gtk_init(&argc, &argv);
}

これをビルドするCMakeLists.txtは以下のようになる。

cmake_minimum_required(VERSION 2.6)
project(Hello C)
add_executable(hello main.c)
find_package(GTK2 REQUIRED)
include_directories(${GTK2_INCLUDE_DIRS})
target_link_libraries(hello ${GTK2_LIBRARIES})

find_packageコマンドを実行すると、指定したライブラリの探索モジュール(FindGTK2)を探し、それを実行する。FindGTK2はそのシステムにあるGTK2を探し、その結果を変数に入れる。この場合、インクルードパスやリンクするべきライブラリがGTK2_INCLUDE_DIRSとGTK2_LIBRARIESにセットされる。あとはinclude_directoriesやtarget_link_librariesを使って指定すればよい。なお、このプログラムにとってGTK2は必須のライブラリなのでREQUIREDを付けている。

find_packageコマンドがセットする変数

モジュールXXXに対し、find_packageがセットする変数はだいたい以下のようになる。意味は見れば分かるだろう。

  • XXX_FOUND
  • XXX_INCLUDE_DIRSまたはXXX_INCLUDES
  • XXX_LIBRARIESまたはXXX_LIBS
  • XXX_DEFINITIONS

XXXの部分はGTK2_FOUND, CURSES_FOUNDなどのように全て大文字であることが多いが、Boost_FOUNDなどのようにキャメルケースになっている場合もある。具体的にどのような変数がセットされるかは、cmake --help-module FindGTK2 とすれば分かる。

find_packageの動作の詳細

find_packageの内部動作には2種類あり、モジュールモードとコンフィグモードと呼ばれている。

find_package(XXX)を呼び出すと、cmakeはまずFindXXX.cmakeというファイルを探す。最初に${CMAKE_MODULE_PATH}で指定されたディレクトリを探し、なければ/share/cmake-x.y/Modules以下(例えば/usr/share/cmake-3.5/Modules以下)を探す。それでもなければXXXConfig.cmakeかxxx-config.cmakeを探す。

FindXXX.cmakeを使うのがモジュールモードで、XXXConfig.cmake/xxx-config.cmakeを使うのがコンフィグモードだ。先の例ではFindGTK2.cmakeを使ったので、モジュールモードを使ったことになる。

モジュールモードとコンフィグモードの違いは単なる優先度の差だけではなく、作成者と処理の内容が(通常は)異なる。モジュールモードは、そのファイル名であるFindXXXという名前が示す通り、指定したライブラリを探すものであり、つまりそのライブラリの作成/インストールした人間以外が書くものだ。

一方コンフィグモードのファイルは探す対象のライブラリ自身によって置かれることを想定している。多分そのライブラリのインストーラなどが置く場合だろう。そのため、XXXConfig.cmake/xxx-config.cmakeは通常はライブラリを「探す」ことはしない。そのライブラリ自身が置いたのであれば、ライブラリがどこにインストールされたのかは知っているはずなので、単にハードコードされた値が書かれているだろう。

ただし、現状はコンフィグモードの.cmakeを置くライブラリはあまりないそうだ。実際、手元のUbuntu-16.04を見てもそのようなファイルは見当たらない。そういうわけで、コンフィグモードを使うのはCMakeがもっと普及したら、あるいは自分で作る場合だけになりそうだ。

pkg_check_modulesを使う

find_packageが使えない場合は探索作業を自分で行うことになる。例えば、手元の環境のcmakeにはFindGTK(GTK1用)とFindGTK2(GTK2用)はあるが、GTK3を探すモジュールがない。仕方ないのでGTK3を自分で探すわけだが、pkg_check_modulesが使えるならそんなに難しくない。

とここでpkg_check_modulesの説明の前に、それが内部で使うpkg-configを説明する。

pkg-configとは

pkg-configはインクルードパスやライブラリパスといった、そのライブラリを使うアプリケーションをビルドするのに必要な情報を提供してくれるツールだ。freedesktop.orgという、Unix系OSのデスクトップ環境の共通仕様・ツールを提供する団体が作ったものらしい(なのでpkg-config自体はCMakeとは無関係)。Linux, *BSD, Mac OS X, WindowsのMSYSといった多くの環境で使えるため、これを使えば簡単にポータブルなビルド環境を作ることができる。

使い方の詳細はmanを見て欲しいが、`pkg-config --cflags `でそのライブラリの利用に必要なコンパイラオプションが表示され、--libsオプションでリンカオプションが表示される。

$ pkg-config --cflags gtk+-3.0
-pthread -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/mirclient -I/usr/include/mircommon -I/usr/include/mircookie -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include

$ pkg-config --libs gtk+-3.0
-lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0

GTK3を使うアプリなら以下のようにすればビルドできる。

$ gcc `pkg-config --cflags gtk+-3.0` main.c `pkg-config --libs gtk+-3.0`

なお、指定可能なライブラリ一覧は`pkg-config --list-all`で表示できる。

CMakeでのpkg-configの利用

で、この便利なpkg-configをCMakeから使えるようにしたのがPkgConfigモジュールだ。使い方はサンプルを見れば分かるだろう。

cmake_minimum_required(VERSION 2.6)
project(Hello C)
add_executable(hello main.c)

find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 gtk+-3.0 REQUIRED)
include_directories(${GTK3_INCLUDE_DIRS})
target_link_libraries(hello ${GTK3_LIBRARIES})

まずはfind_packageでこのモジュールを見つける。実際にpkg-configを使うのはpkg_check_modulesコマンドで、pkg_check_modules( )のように指定する。は`pkg-config --list-all`で出てくる名前で、は結果を格納する変数の接頭辞だ。ここでは"GTK3"としたので、GTK3_INCLUDE_DIRSなどの変数がセットされる。セットされる変数一覧はここ

見ての通り、FindXXXを使った場合とたいして違いはない。実際、デフォルトでインストールされるFindXXX.cmakeも内部ではpkg_check_modulesを使っているものもある。

FindXXX.cmakeをコピーして使う

find_packageが使えない場合のもう1つの解決法として、他人が作ったコマンドをコピーして使うというのがある。ということでネット上を探してみる。

あるとすれば恐らくFindGTK3.cmakeという名前なので、その名前でググってみるといくつかそれっぽいのもが出てくる。中でもChromiumに含まれているものなら信頼できそうな感じがするので、これをコピーしてFindGTK3.cmakeという名前で保存しよう。

(念のため書くが、コードをパクるときはライセンスを確認しよう。とは言っても、出来上がるバイナリには含まれないビルドツール用のコードをコピーした場合はどうなるんだ?)

とりあえず、プロジェクトのトップにcmakeディレクトリを作り、その中に置いてみた。

test/
  +- CMakeLists.txt
  +- main.c
  +- cmake/
       +- FindGTK3.cmake

CMakeLists.txtは以下のようになる。

cmake_minimum_required(VERSION 2.6)
project(Hello C)
add_executable(hello main.c)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(GTK3 REQUIRED)
include_directories(${GTK3_INCLUDE_DIRS})
target_link_libraries(hello ${GTK3_LIBRARIES})

自分で置いたFindXXX.cmakeを使う場合、CMAKE_MODULE_PATHを指定する必要がある。上の方にも書いたとおり、モジュールの探索はCMAKE_MODULE_PATHとCMakeがインストールされたディレクトリのModules以下のみで、たとえCMakeLists.txtと同じディレクトリに置いたとしても自動的に読んではくれない。

REQUIREDとQUIET

find_package, pkg_check_modules共通のオプションにREQUIREDとQUIETがある。find_packageやpkg_check_modulesにREQUIREDを指定しないと、見つからなかった場合にメッセージは出すが処理は続行する。REQUIREDを指定すると見つからなかった場合は処理がそこで止まり、cmakeコマンド自体の戻り値も1になる。また、QUIETを指定すると見つからなかった場合のメッセージ出力が抑制される。

なお、find_packageにREQUIREDを指定しなかった場合でも、FindXXX.cmake自体が見つからなかった場合はエラーとなり、そこで処理は中止される。

以下に例を挙げる。なお手元の環境にはQt4が入っていない。

find_package(PkgConfig REQUIRED)
pkg_check_modules(XXX xxx)          # xxxというモジュールはないためエラーメッ
                                    # セージが出力されるが処理は続行
pkg_check_modules(XXX xxx QUIET)    # エラーメッセージを出さずに続行
pkg_check_modules(XXX xxx REQUIRED) # エラーとなり処理が中断される
find_package(Qt4)                   # エラーメッセージを出力して処理続行
find_package(Qt4 QUIET)             # エラーメッセージを出さずに続行
find_package(Qt4 REQUIRED)          # エラーとなり処理が中断される
find_package(YYY)                   # FindYYY.cmakeがないため、REQUIREDがついて
                                    # いなくてもエラー終了

参考資料

CMake:How To Find Libraries
https://cmake.org/Wiki/CMake:How_To_Find_Libraries
find_packageの公式マニュアル
https://cmake.org/cmake/help/v3.0/command/find_package.html
pkg_check_modulesの公式マニュアル
https://cmake.org/cmake/help/v3.0/module/FindPkgConfig.html