TL;DR: Why does leaving CMAKE_SYSROOT
unset in a toolchain file cause CMake to ignore CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
and allow find_package
to search CMAKE_PREFIX_PATH
anyway?
Context: I’m playing around with cross-compilation using the Conan package manager.
Conan writes packagename-config.cmake
files for all dependencies in a build directory, and then provides a CMake toolchain file that adds this build directory to CMAKE_PREFIX_PATH
, so that find_package()
picks up the config.cmake
files generated by Conan. So far, so good.
Unexpected observation #1:
However, when cross-compiling, I use the following in my toolchain file, to avoid accidentally picking up packages from the machine I’m building on:
set(CMAKE_SYSTEM_NAME "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)
The strange thing is that CMake still picks up Conan’s packages, even though they are only available in CMAKE_PREFIX_PATH
, which I would expect to be skipped during cross-compilation.
It seems that the Conan devs rely on this themselves, though, for example in these tests: conan/test_cmaketoolchain_paths.py at d66a3c00ef66947ec8c9e0b2bfba69cfcdce7387 · conan-io/conan · GitHub
Unexpected observation #2:
However, when also setting CMAKE_SYSROOT
in the toolchain file, suddenly, find_package
no longer considers the Conan config.cmake
files in CMAKE_PREFIX_PATH
. This seems to me the expected behavior, but it is unexpected to me that simply setting CMAKE_SYSROOT
changes the behavior of find_package
for CMAKE_PREFIX_PATH
.
As a consequence, cross-compilation using Conan is broken when both CMAKE_SYSROOT
and CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
are set in the user’s toolchain file.
When editing the toolchain file generated by Conan, replacing CMAKE_PREFIX_PATH
to CMAKE_FIND_ROOT_PATH
, it picks up the Conan config.cmake
files again, as expected.
Some final questions:
- Should the Conan devs consider setting
CMAKE_FIND_ROOT_PATH
instead ofCMAKE_PREFIX_PATH
to make sure CMake locates theconfig.cmake
files it generated? - The Mastering CMake chapter on cross-compilation does not set
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
. Why not? Is it ever acceptable to select packages from the build machine when cross-compiling? (Ignoring cases where the user explicitly allows it at thefind_package
call site, e.g. when they know that the package is header-only.)
Files to reproduce:
The following files can be used to reproduce the problem, without using Conan.
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(cmake-prefix-tests)
list(PREPEND CMAKE_PREFIX_PATH ${PROJECT_SOURCE_DIR}/packages)
# list(PREPEND CMAKE_FIND_ROOT_PATH ${PROJECT_SOURCE_DIR}/packages)
find_package(mypackage REQUIRED)
packages/mypackage-config.cmake
message(STATUS "FOUND: ${CMAKE_CURRENT_LIST_FILE}")
toolchainA.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER cc)
# set(CMAKE_SYSROOT "${CMAKE_CURRENT_LIST_DIR}/sysroot")
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)
toolchainB.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER cc)
set(CMAKE_SYSROOT "${CMAKE_CURRENT_LIST_DIR}/sysroot")
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)
$ rm -rf build; cmake -S. -Bbuild --toolchain toolchainA.cmake
-- FOUND: /home/.../packages/mypackage-config.cmake
$ rm -rf build; cmake -S. -Bbuild --toolchain toolchainB.cmake
Could not find a package configuration file provided by "mypackage" with
any of the following names:
mypackageConfig.cmake
mypackage-config.cmake