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_* の設定 を確認します。

参考