CMakeのクロスコンパイルでsysrootを設定する理由
クロスコンパイルでは、コンパイラを指定しただけでは足りないことがあります。
特に困りやすいのが、ヘッダーやライブラリをどこから探すかです。ホストの /usr/include や /usr/lib を見てしまうと、ビルドは通ってもターゲット環境では動かない、という状態になりがちです。
そこで出てくるのが CMAKE_SYSROOT です。
https://cmake.org/cmake/help/latest/variable/CMAKE_SYSROOT.html
toolchain file自体の最小構成は CMakeでクロスコンパイルするときのtoolchain fileの書き方 に分けています。この記事では、その次に出てくるsysrootだけを扱います。
sysrootとは
sysrootは、ターゲット環境のルートファイルシステムのようなものです。
たとえばARM Linux向けにビルドするなら、ターゲット側のヘッダーやライブラリを含むディレクトリをホスト側に用意しておきます。
sysroots/
└── arm-linux/
├── usr/
│ ├── include/
│ └── lib/
└── lib/
CMakeには、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++)
これで、CMakeやコンパイラがターゲット用のヘッダー、ライブラリを探しやすくなります。
CMAKE_SYSROOTを使う場面
CMAKE_SYSROOT は、ターゲット環境のファイル一式がホスト側にあるときに使います。
代表的には以下です。
- Raspberry Pi向けにLinux PCでビルドする
- 組み込みLinux向けにアプリをビルドする
- ターゲット端末からrootfsをコピーして使う
- SDKがsysrootを提供している
CMake公式のtoolchain例でも、クロスコンパイル用のtoolchain fileに CMAKE_SYSROOT が出てきます。
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-linux
install先とは分けて考える
sysrootとinstall先は別物です。
| 変数 | 役割 |
|---|---|
CMAKE_SYSROOT |
ビルド時に参照するターゲット環境 |
CMAKE_INSTALL_PREFIX |
ターゲット上でのインストール先 |
CMAKE_STAGING_PREFIX |
ホスト上で一時的にinstallする場所 |
クロスコンパイルでは、成果物を一度ホスト側のディレクトリにinstallしてから、ターゲットへ転送することがあります。
その場合は CMAKE_STAGING_PREFIX を使います。
set(CMAKE_SYSROOT /opt/sysroots/arm-linux)
set(CMAKE_STAGING_PREFIX /opt/stage/my-app)
set(CMAKE_INSTALL_PREFIX /usr/local)
この場合、ホスト上では /opt/stage/my-app に配置しつつ、ターゲット上では /usr/local に入る前提で扱います。
find系にも影響する
find_library() や find_path()、find_package() を使う場合、どこを探すかが問題になります。
CMakeのtoolchainドキュメントでは、クロスコンパイル時に CMAKE_FIND_ROOT_PATH_MODE_* を使って探す場所を制御する例が出ています。
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-linux
典型的には、プログラムはホスト側、ライブラリやヘッダーはターゲット側から探す設定にします。
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_packageがホストを見に行くときの設定 で find_package() 側から整理しています。
よくある詰まりどころ
ホスト側のライブラリを拾っている
クロスコンパイルでありがちなのが、ホスト側の /usr/lib にあるライブラリを拾ってしまうパターンです。
ターゲット向けにビルドしているのに、x86_64用のライブラリをリンクしようとして失敗します。
この場合は、sysrootと CMAKE_FIND_ROOT_PATH_MODE_LIBRARY を確認します。
sysrootの中身が足りない
sysrootにヘッダーだけあってライブラリがない、またはその逆の状態でも失敗します。
まずは目的のファイルがsysroot内に存在するか確認します。
find /opt/sysroots/arm-linux -name 'libssl*'
find /opt/sysroots/arm-linux -name 'openssl'
CMake設定を見る前に、sysroot自体を確認した方が早いことも多いです。
まとめ
クロスコンパイルでは、コンパイラだけでなく、ターゲット環境のヘッダーやライブラリも指定する必要があります。
CMAKE_SYSROOTはターゲット環境のルートを指定するCMAKE_INSTALL_PREFIXとは役割が違う- ホストに一時installする場合は
CMAKE_STAGING_PREFIXを使う - find系の探索先は
CMAKE_FIND_ROOT_PATH_MODE_*で調整する
クロスコンパイルで謎のリンクエラーが出る場合、sysrootの設定と中身を先に見ると整理しやすいです。
find_package() がホスト側を見に行っている場合は、続けて CMAKE_FIND_ROOT_PATH_MODE_* の設定 を確認します。


