qmake で依存ライブラリの有無をチェックし、結果に応じて処理を変えよう


この記事は Qt Advent Calendar 2016 5日目の記事になります。

@taskjp です。1日目 ぶりですね。あれ以来 色々頑張っていますよー

昨日は @hermit4 さんの Qt Creator を好みの見た目にカスタマイズしよう
Qt
という記事でした。

Qt Creator 1.0 くらいのころは下記の画像の赤丸のエリアをダブルクリックする だけで モードセレクタの色 が変えられて便利だったんですけどなぜ無くなった。。。

それはさておき、今日はすこし真面目に Qt を使う上で役に立つ話を書こうと思います。

はじめに

あるプロジェクトに以下のようなコードがありました

あるプロジェクト.pro
contains(QT_ARCH, arm.*) {
    INCLUDEPATH += 別のライブラリのヘッダファイルの場所
    LIBS += -L別のライブラリの場所 -l別のライブラリ
}
とあるプロジェクト.cpp
#ifdef __arm__
#include <別のライブラリのヘッダファイル>
#endif

#ifdef __arm__
    // 別のライブラリの関数の呼び出し
#endif

このコードを書いた人に、
なんで arm かどうかで処理分けてるの?
と尋ねたところ、
だってその別のライブラリは arm 上でしか用意されてないんだもん
と言われました。

それ頭おかしい その別のライブラリは別にアーキテクチャに依存しているわけじゃないし、開発機でビルドしたらそれなりに動くし、(Gentoo 用の ebuild 書いたし)、なんかもっといい方法があるんじゃないの?

というわけで、本日は qmake で 依存関係をテストして、結果に応じて処理を変える 方法を紹介したいと思います。

load(feature)

load 関数は引数で指定された名前の .prf(プロジェクト関数)ファイルを呼び出します。

configure と指定した場合には(余計なことをしていない場合は) qtbase/mkspecs/features/configure.prf が読み込まれます。

configure.prfの一部を抜粋
# Try to build the test project in $$QMAKE_CONFIG_TESTS_DIR/$$1
# ($$_PRO_FILE_PWD_/config.tests/$$1 by default).
#
# If the test passes, config_$$1 will be added to CONFIG.
# The result is automatically cached. Use of cached results
# can be suppressed by passing CONFIG+=recheck to qmake.
#
# Returns: true iff the test passes
defineTest(qtCompileTest) {
   # 指定されたテストの実施
}

configure.prf をロードすることで、qtCompileTest(test) という関数が使えるようになります。
この関数は $$QMAKE_CONFIG_TESTS_DIR / 以下の引数で指定されたディレクトリ以下にあるプロジェクトをビルドし、実行します。
$$QMAKE_CONFIG_TESTS_DIR はデフォルトでは $$_PRO_FILE_PWD_/config.tests となっています。

テストを書いてみよう

load(configure) を書いたプロジェクトファイルと同じディレクトリに config.tests というディレクトリを作成し、その下に依存関係のテスト用のプロジェクトを作成します。

config.tests/libhoge/libhoge.pro
SOURCES = libhoge.cpp

CONFIG -= qt
CONFIG += link_pkgconfig
PKGCONFIG += hoge
config.tests/libhoge/libhoge.cpp
#include <hoge.h>

int main(int argc,char **argv)
{
    Hoge hoge;
    if (!hoge.isValid()) return -1;
    return 0;
}

今回は Linux でよく使われる pkg-config という仕組みを使って依存パッケージ hoge の有無をチェックするための qmake のプロジェクトを作りました。
pkg-config 的に hoge が見つからない場合や、libhoge.cpp のコンパイルに失敗する場合、libhoge を実行して 0 以外のコードが返ってくる場合にはエラーとみなします。

メインのプロジェクトファイルでは load(configure) をした後、qtCompileTest() を使用して libhoge のテストを行います。

テストが成功した場合には qtCompileTest() 内で CONFIG += libhoge というコードが走るため、以下のような形で成否で分岐することができるようになります。

また、ヘッダファイルやソースファイルでも判別できるように、.pro の中で専用のマクロを定義しています。

main.pro
load(configure)
qtCompileTest(libhoge)

config_libhoge {
    CONFIG += link_pkgconfig
    PKGCONFIG += hoge
    DEFINES += HAVE_LIBHOGE
}

ソースコードはこんな感じになります。

main.cpp
#ifdef HAVE_LIBHOGE
#include <hoge.h>
#endif

...

#ifdef HAVE_LIBHOGE
    Hoge hoge;
    hoge.exec();
#endif

実行

qmake 時に以下のようなログが出力されます。

$ qmake
...
Checking for libhoge... yes
...

おわりに

「ARM の時はこの機能を使う」コードより、「この機能が使えるときはこの機能を使う」方が直感的でわかりやすいですね。

この仕組みは qtmultimedia/qtmultimedia.proqtmultimedia/config.tests/ など、Qt のモジュールでも使われている機能ですので、この辺も参考にしてもらえればと思います。

ではまた。