Standalone Boot2Qt / Yocto SDK CMake toolchain

Cross-compiling with CMake is easy, you just need to pass the toolchain file as a parameter when you configure your project like: -DCMAKE_TOOLCHAIN_FILE=/path/to/your/toolchain.cmake

If we look at CMake’s documentation about toolchains we can see the description of a toolchain file:

CMake uses a toolchain of utilities to compile, link libraries and create archives, and other tasks to drive the build. The toolchain utilities available are determined by the languages enabled. In normal builds, CMake automatically determines the toolchain for host builds based on system introspection and defaults. In cross-compiling scenarios, a toolchain file may be specified with information about compiler and utility paths.

We also have an example of a typical cross-compiling Linux toolchain:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs)
set(CMAKE_STAGING_PREFIX /home/devel/stage)

set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/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)

Notice that the toolchain has just a few variables being set, mostly the sysroot and the compiler. Nothing about compiler / linker flags.

Boot2Qt and Yocto SDK

Boot2Qt for Embeeded Linux is based on Yocto Project.

While Yocto SDK provides a CMake toolchain file, in my case at /opt/b2qt/2.7.2/sysroots/x86_64-pokysdk-linux/usr/share/cmake/OEToolchainConfig.cmake, it also requires having to source a shell script which will set various environment variables, namely /opt/b2qt/2.7.2/environment-setup-armv7at2hf-neon-poky-linux-gnueabi.

This can be noticed if we look at OEToolchainConfig.cmake and see the $ENV{variable} references:

set( CMAKE_SYSTEM_NAME Linux )
set( CMAKE_C_FLAGS $ENV{CFLAGS} CACHE STRING "" FORCE )
set( CMAKE_CXX_FLAGS $ENV{CXXFLAGS}  CACHE STRING "" FORCE )
set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "" FORCE )
set( CMAKE_LDFLAGS_FLAGS ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE )
set( CMAKE_SYSROOT $ENV{OECORE_TARGET_SYSROOT} )

set( CMAKE_FIND_ROOT_PATH $ENV{OECORE_TARGET_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 )

set(CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX "$ENV{OE_CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX}")

# Set CMAKE_SYSTEM_PROCESSOR from the sysroot name (assuming processor-distro-os).
if ($ENV{SDKTARGETSYSROOT} MATCHES "/sysroots/([a-zA-Z0-9_-]+)-.+-.+")
  set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_MATCH_1})
endif()

# Include the toolchain configuration subscripts
file( GLOB toolchain_config_files "${CMAKE_TOOLCHAIN_FILE}.d/*.cmake" )
foreach(config ${toolchain_config_files})
    include(${config})
endforeach()

The toolchain does include other files via the file globbing and the foreach code at the end.

/opt/b2qt/2.7.2/sysroots/x86_64-pokysdk-linux/usr/share/cmake/OEToolchainConfig.cmake.d/qemuarmv7.cmake contains the following code:

set(CMAKE_SYSROOT /opt/b2qt/2.7.2/sysroots/armv7at2hf-neon-poky-linux-gnueabi)
set(CMAKE_PREFIX_PATH /opt/b2qt/2.7.2/sysroots/armv7at2hf-neon-poky-linux-gnueabi/usr/lib/cmake)
set(compiler_flags " -march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security")
set(CMAKE_C_COMPILER_ARG1 "${compiler_flags}")
set(CMAKE_CXX_COMPILER_ARG1 "${compiler_flags}")
set(OE_QMAKE_PATH_EXTERNAL_HOST_BINS /opt/b2qt/2.7.2/sysroots/x86_64-pokysdk-linux/usr/bin)

The host tools of Boot2Qt / Yocto SDK include also a version of CMake, ninja build tool and other goodies.

As it turns out the CMake version bundled there is 3.14, which is not enough to build Qt6! 

Standalone toolchain

Wouldn’t it be easier to understand what’s going on in a toolchain file if everything was put into one file? Not having to source shell scripts and using the tools you already have installed in your host system?

Here is my version of arm-poky-linux-gnueabi.cmake:

cmake_minimum_required(VERSION 3.11)
include_guard(GLOBAL)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(B2QT /opt/b2qt/2.7.2)
set(POKY_TARGET_SYSROOT ${B2QT}/sysroots/armv7at2hf-neon-poky-linux-gnueabi)
set(POKY_HOST_TOOLS ${B2QT}/sysroots/x86_64-pokysdk-linux)

set(CMAKE_SYSROOT ${POKY_TARGET_SYSROOT})
set(CMAKE_PROGRAM_PATH ${POKY_HOST_TOOLS}/usr/bin)

set(CMAKE_C_COMPILER ${POKY_HOST_TOOLS}/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${POKY_HOST_TOOLS}/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g++)

set(POKY_COMPILER_FLAGS "-march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security")
set(POKY_COMPILER_FLAGS_RELEASE "-O2 -pipe -g -feliminate-unused-debug-types")
set(POKY_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed")

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)

include(CMakeInitializeConfigs)

function(cmake_initialize_per_config_variable _PREFIX _DOCSTRING)
  if (_PREFIX MATCHES "CMAKE_(C|CXX|ASM)_FLAGS")
    set(CMAKE_${CMAKE_MATCH_1}_FLAGS_INIT "${POKY_COMPILER_FLAGS}")

    foreach (config DEBUG RELEASE MINSIZEREL RELWITHDEBINFO)
      if (DEFINED POKY_COMPILER_FLAGS_${config})
        set(CMAKE_${CMAKE_MATCH_1}_FLAGS_${config}_INIT "${POKY_COMPILER_FLAGS_${config}}")
      endif()
    endforeach()
  endif()

  if (_PREFIX MATCHES "CMAKE_(SHARED|MODULE|EXE)_LINKER_FLAGS")
    foreach (config SHARED MODULE EXE)
      set(CMAKE_${config}_LINKER_FLAGS_INIT "${POKY_LINKER_FLAGS}")
    endforeach()
  endif()

  _cmake_initialize_per_config_variable(${ARGV})
endfunction()

CMake provides the CMAKE_<LANG>_FLAGS_<CONFIG>_INIT variables in order to populate the CMAKE_CXX_FLAGS_RELEASE variable for example.

But there is one caveat, CMake will append its own default values for the various build types, as seen at Modules/Compilers/GNU.cmake:

  # Initial configuration flags.
  string(APPEND CMAKE_${lang}_FLAGS_INIT " ")
  string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT " -g")
  string(APPEND CMAKE_${lang}_FLAGS_MINSIZEREL_INIT " -Os -DNDEBUG")
  string(APPEND CMAKE_${lang}_FLAGS_RELEASE_INIT " -O3 -DNDEBUG")
  string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " -O2 -g -DNDEBUG")

With the trick of overloading cmake_initialize_per_config_variable function (available since CMake 3.11), we can modify the CMAKE_<LANG>_FLAGS_<CONFIG>_INIT variables and we don’t have to resort to other hacks like:

set( CMAKE_C_FLAGS $ENV{CFLAGS} CACHE STRING "" FORCE )
set( CMAKE_CXX_FLAGS $ENV{CXXFLAGS}  CACHE STRING "" FORCE )
set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "" FORCE )
set( CMAKE_LDFLAGS_FLAGS ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE )

For more information about the cmake_initialize_per_config_variable trick, have a look at Modifying the default CMake build types article on my personal blog.

Conclusion

The standalone Boot2Qt / Poky CMake toolchain file looks very similar to the typical cross-compiling Linux toolchain file.

The compiler and linker flags are provided in one place and the usage is as simple as providing a -DCMAKE_TOOLCHAIN_FILE=/path/to/your/toolchain.cmake parameter to your CMake configuration step!


Comments