CMakeのクロスコンパイルでfind_packageがホストを見に行くときの設定
CMakeでクロスコンパイルしていると、find_package() が思った場所を見てくれないことがあります。
ターゲット用のライブラリを探してほしいのに、ホスト側の /usr/lib や /usr/local を見に行く。逆に、ビルド中に実行するホスト側ツールまでsysroot内から探そうとする。どちらもクロスコンパイルではよく出る問題です。
このあたりは CMAKE_FIND_ROOT_PATH_MODE_* で制御します。
公式のtoolchainドキュメントにも、クロスコンパイル時の典型例として設定が載っています。
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-linux
前提となる toolchain file は CMakeでクロスコンパイルするときのtoolchain fileの書き方 に、sysrootの考え方は CMakeのクロスコンパイルでsysrootを設定する理由 に分けています。
典型的な設定
toolchain fileに以下のように書きます。
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
ざっくり言うと、こういう考え方です。
| 変数 | 探すもの | よくある設定 |
|---|---|---|
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM |
ビルド中に実行するプログラム | NEVER |
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY |
リンクするライブラリ | ONLY |
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE |
includeするヘッダー | ONLY |
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE |
find_package() のパッケージ |
ONLY |
ビルド中に実行するプログラムはホスト側のものを使います。
一方で、リンクするライブラリやincludeするヘッダーはターゲット側のものを使います。
PROGRAMだけNEVERにする理由
たとえばビルド中に protoc やコード生成ツールを実行する場合、そのツールはホスト上で動く必要があります。
ターゲットがARM Linuxで、ホストがx86_64 Linuxなら、ARM向けの protoc を見つけてもホストでは実行できません。
そのため、プログラム探索はホスト側を見るようにします。
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
この変数の説明は公式にもあります。
https://cmake.org/cmake/help/latest/variable/CMAKE_FIND_ROOT_PATH_MODE_PROGRAM.html
ライブラリとヘッダーはONLYにする
リンクするライブラリとincludeするヘッダーはターゲット側のものが必要です。
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
ライブラリの探索モードは CMAKE_FIND_ROOT_PATH_MODE_LIBRARY で制御します。
https://cmake.org/cmake/help/latest/variable/CMAKE_FIND_ROOT_PATH_MODE_LIBRARY.html
ここが曖昧だと、ホスト側のライブラリを拾ってしまうことがあります。
コンパイルは進んでもリンクでアーキテクチャ違いのエラーが出る場合は、このあたりを疑います。
find_packageもターゲット側を見る
find_package() は、ライブラリ本体だけでなく xxxConfig.cmake などのパッケージ設定ファイルを探します。
クロスコンパイル時にターゲット用のパッケージ設定を使いたいなら、次のようにします。
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
公式変数はこちらです。
https://cmake.org/cmake/help/latest/variable/CMAKE_FIND_ROOT_PATH_MODE_PACKAGE.html
たとえばsysroot内に次のようなファイルがある場合、そこを見てほしいということです。
/opt/sysroots/arm-linux/usr/lib/cmake/ZLIB/ZLIBConfig.cmake
ホスト側に入っているZLIBを拾うと、ターゲット向けのビルドとしては崩れます。
toolchain fileの例
まとめると、toolchain fileはこのような形になります。
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT /opt/sysroots/arm-linux)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
この設定で、プログラムはホスト側、ライブラリ・ヘッダー・パッケージはターゲット側を優先する形になります。
うまくいかないときに見る場所
find_package() がどこを見ているか分からない場合は、まずCMakeのログを増やします。
cmake -S . -B build-arm \
--toolchain toolchains/arm-linux.cmake \
--debug-find
--debug-find を付けると、find系コマンドがどこを探しているか追いやすくなります。
ホスト側の /usr/lib を見ているのか、sysroot内を見ているのかを確認します。
外部ライブラリをそもそも find_package() で扱うか FetchContent で取得するか迷う場合は、CMakeでFetchContentとfind_packageを使い分ける目安 も近い内容です。
まとめ
クロスコンパイルで find_package() が変な場所を見るときは、探索対象を分けて考えます。
- ビルド中に実行するプログラムはホスト側
- リンクするライブラリはターゲット側
- includeするヘッダーはターゲット側
find_package()の設定ファイルもターゲット側
toolchain fileでは、CMAKE_FIND_ROOT_PATH_MODE_* をまとめて設定しておくと、後から原因を追いやすくなります。


