CMAKE_FIND_ROOT_PATH and toolchain files

After taking a look at the docs and the 8th edition of Professional CMake I am not sure what’s the intended usage, if any, of CMAKE_FIND_ROOT_PATH inside a toolchain file.

For the simple case you can argue “just use CMAKE_SYSROOT”. If you have some more complex needs “use CMAKE_STAGING_PREFIX”. This seems to be what the authors had in mind when writing the docs, but it’s never explicitly said “CMAKE_FIND_ROOT_PATH is not toolchain file stuff”.

Probably because CMAKE_SYSROOT has other effects (adds --sysroot to the compiler run), and the compiler may have already been built with the correct sysroot and people don’t want the extra characters in their build logs, I have seen cases where the toolchain file sets CMAKE_FIND_ROOT_PATH instead. Now, the toolchain doesn’t append or prepend to CMAKE_FIND_ROOT_PATH, it sets it. Meaning it overwrites any user provided CMAKE_FIND_ROOT_PATH.

Personally I like to have different libraries installed in different paths. Mainly so the dependencies are always explicitly set. Otherwise it’s too easy to find_package(X)… and start using X, Y, Z and ten other packages all installed in there. So I end up with a big CMAKE_FIND_ROOT_PATH list I pass to my project.

I could open a bug report for those toolchain files. But, I’m not really sure they are doing anything wrong. I can’t find anywhere in the docs anything saying whether toolchains are free to set CMAKE_FIND_ROOT_PATH or whether they need to accommodate user provided CMAKE_FIND_ROOT_PATH.

Does anybody know what the intention was?

Both Buildroot (toolchainfile.cmake.in\misc\support - buildroot - Buildroot: Making Embedded Linux easy) and OpenEmbedded/Yocto Project (openembedded-core/OEToolchainConfig.cmake at master · openembedded/openembedded-core · GitHub) do it. But in their cases it looks like they started using CMAKE_FIND_ROOT_PATH, they later added CMAKE_SYSROOT and forgot(?) to remove CMAKE_FIND_ROOT_PATH.

In the case of Android NDK an issue was reported (NDK cmake toolchain overrides CMAKE_FIND_ROOT_PATH · Issue #912 · android/ndk · GitHub) and then fixed by keep touching CMAKE_FIND_ROOT_PATH, but only appending to it.

I can’t speak for the original intention for CMAKE_FIND_ROOT_PATH, but your interpretation seems ok to me. CMAKE_FIND_ROOT_PATH can be used to make the various find_...() commands aware of directories below which they can find things for the target platform. It is similar to a sysroot, but only applies to searching for things and does not affect compiler flags like CMAKE_SYSROOT does. A use case where CMAKE_FIND_ROOT_PATH is appropriate is precisely your scenario, where you have some things pre-built for the target platform and they are not part of the sysroot that the toolchain itself might provide. An example might be dependency projects you’ve built earlier and can be found at some arbitrary location in your file system.

As to whether a toolchain file should set this, that likely depends on how the toolchain’s directory structure is laid out. If the tooling is separated out from the sysroot into a different area (I think this is relatively common), then CMAKE_FIND_ROOT_PATH may need to be modified so that the tools can be found by various find_...() commands. The NDK does this. The toolchain file shouldn’t discard a pre-existing value for CMAKE_FIND_ROOT_PATH though, it should only append to it. If a toolchain file prepends instead of appends, I’d consider that an error because the user should always have the ability to override the toolchain (e.g. they may want to use a more recent version of a tool or library that the toolchain itself provides).

And just for good measure, there’s an annoying bug regarding the interaction between CMAKE_FIND_ROOT_PATH and CMAKE_PREFIX_PATH which can thwart you in specific circumstances. Issue 21937 has the details (the Qt project provides a toolchain file for convenience and has to work around this specific behavior).

How should such a toolchain look like?

$ cat CMakeLists.txt 
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
project(root_path_test)
message(STATUS "CMAKE_FIND_ROOT_PATH: ${CMAKE_FIND_ROOT_PATH}")
$ cat test.toolchain.cmake 
list(APPEND CMAKE_FIND_ROOT_PATH "a" "b")
message(STATUS "Pass")

Shows duplicated paths in the first run

$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=test.toolchain.cmake -DCMAKE_FIND_ROOT_PATH="c;d"
-- Pass
-- Pass
-- The C compiler identification is GNU 11.1.1
-- The CXX compiler identification is GNU 11.1.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- CMAKE_FIND_ROOT_PATH: c;d;a;b;a;b
-- Configuring done
-- Generating done
-- Build files have been written to: build

Which is not the end of the world, but…

I guess you could check if CMAKE_FIND_ROOT_PATH already had the path you want to add or not and only add it if it isn’t already there.