見切り発車

とりあえずかきとめたい

Windows 環境でのCMake のfind_package() 数珠繋ぎを EXPORT_PACKAGE_DEPENDENCIES で解決する

まとめ

  • CMake で間接的に参照するパッケージはinstall(EXPORT) のEXPORT_PACKAGE_DEPENDENCIES オプションを指定すると解決してくれる
  • EXPORT_PACKAGE_DEPENDENCIES を指定するためにはset(CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_DEPENDENCIES 実験的機能のUUID) が必要
  • 実験的機能のUUID はCMake のcmExperimental.cxx に書いてあり、定期的に変更されるらしい

本文

CMake で作成したライブラリのプロジェクトを、別のCMake プロジェクトから利用する場合、find_package() を利用するのが良いようです。

自分ではこれまでわりとgit submodule add でプロジェクトのツリーに取り込んでadd_subdirectory() で組み込むということをやってたのですが、find_package() を利用するほうが良さそうだということを学び始めました。

自作パッケージをCMakeのfind_package()に対応させる
お手軽な xxx-config.cmake の作成方法
【モダンCMake】つくったC++ライブラリを簡単にインストールできるようにする

特にこのあたりの記事や、他のプロジェクトを参考にさせていただき

  • CMake がfind_package() の時に検索するルールに沿ったパスにパッケージ名-config.cmake ファイルを置く
  • パッケージ名-config.cmake はinstall(EXPORT) コマンドでだいたい自動的に生成できる

といったことがわかりました。

しかし、いざ自分の作成したライブラリを他から利用しようとした場合に「パッケージが見つからない」というエラーが出てきてしまいました。

プロジェクトが以下のような依存関係で、app からfind_package(libA) とした場合にlibB が見つからない、ということでした。

graph RL;
    app-->libA;
    libA-->libB;

全部同じCMake プロジェクトにしていた場合にはtarget_link_libraries() に指定したものは勝手に解決してくれていたのですがパッケージが分かれているとそうはいかないようです。

app プロジェクト内でfind_package(libB) とすれば解決するようですが、app が直接依存しているのはlibA なのでできれば避けたい方法です。いくらか試行錯誤したらどうにかなったので結果をGitHub のリポジトリ に置きました。細かい説明はリポジトリのREADME.md に書いています。

EXPORT_PACKAGE_DEPENDENCIES を有効化する方法については今回調べたなかでも特に情報が見つけられず、CMake のソースコードから調べたのでここにまとめます。


CMake のinstall(EXPORT) の説明 には

Note Experimental. Gated by CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_DEPENDENCIES.

と記載されていますが、ON とかTRUE を設定してもEXPORT_PACKAGE_DEPENDENCIES が未定義というエラーは解消しません。CMake のドキュメントでexperimental などと検索しても関係なさそうなものがいろいろと出てくるためソースコードから調べることにしました。

EXPORT_PACKAGE_DEPENDENCIES で検索するとcmInstallCommand.cxx で

  if (cmExperimental::HasSupportEnabled(
        status.GetMakefile(),
        cmExperimental::Feature::ExportPackageDependencies)) {
    ica.Bind("EXPORT_PACKAGE_DEPENDENCIES"_s, exportPackageDependencies);
  }

のような箇所がありました。そしてHasSupportEnabled の中身はcmExperimental.cxx で

bool cmExperimental::HasSupportEnabled(cmMakefile const& mf, Feature f)
{
  bool enabled = false;
  auto& data = ::DataForFeature(f);

  auto value = mf.GetDefinition(data.Variable);
  if (value == data.Uuid) {
    enabled = true;
  }

  if (enabled && !data.Warned) {
    mf.IssueMessage(MessageType::AUTHOR_WARNING, data.Description);
    data.Warned = true;
  }

  return enabled;
}

となっています。data.UUid はおなじcmExperimental.cxx で定義されていて、

/*
 * The `Uuid` fields of these objects should change periodically.
 * Search for other instances to keep the documentation and test suite
 * up-to-date.
 */
cmExperimental::FeatureData LookupTable[] = {
  // ExportPackageDependencies
  { "ExportPackageDependencies",
    "1942b4fa-b2c5-4546-9385-83f254070067",
    "CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_DEPENDENCIES",
    "CMake's EXPORT_PACKAGE_DEPENDENCIES support is experimental. It is meant "
    "only for experimentation and feedback to CMake developers.",
    {},
    cmExperimental::TryCompileCondition::Always,
    false },

となっていました(確認時)。コメントにUuid フィールドは定期的に変更されるとあります。

CMake の実験的機能を使うにはソースコードを見てUUID を調べる必要がある、というのが正解なのかどうかは分かってませんが、動きはしました……

実際の利用箇所はこちらのCMakeLists.txt です。


その他の感想として、cmake --install (make install) のような作法はWindows だとあまり馴染みが無いなーと思いました。

*-config.cmake のパスについても、CMAKE_INSTALL_PREFIX の説明にあるデフォルト値とConfig Mode Search Procedure の組み合わせで考えると、UINX(Linux) 系は/usr/local/lib/cmake/パッケージ名 となってそれっぽく見えます1Windows 系だとC:\Program Files\${PROJECT_NAME} になっていて、あんまりライブラリをインストールする先っぽくないし普通のユーザーだと書き込み権限もなかったりして、なんというか微妙な感じがします。

Windows 環境でも/usr/local のようなディレクトリの運用をしてみる、とか検討してみてもいいかな?とも思いましたがプロジェクト内で完結していたほうが手軽な気もするなあ。


  1. /usr/local に一般ユーザーの書き込み権限があるかとかはよく分かってません……