Lesson 26 · Quality & debugging

Cross-compiling

Build for another platform, ARM, Android, iOS, with a toolchain file. The key variables and a real Raspberry Pi example.

Normally CMake figures out your compiler by asking your own system. Cross-compiling means building for a different target, an ARM board, an Android phone, a game console, using a compiler that runs on your machine but produces code for the target. A toolchain file tells CMake everything about that setup.

The core idea

cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=arm-gcc.cmake

CMake loads the toolchain file early, before it probes for compilers, and the file answers the questions CMake would otherwise ask your host system:

  • What platform are we building for? (CMAKE_SYSTEM_NAME)
  • Which compilers? (CMAKE_CXX_COMPILER)
  • Where are the target’s libraries and headers? (CMAKE_SYSROOT)

The moment you set CMAKE_SYSTEM_NAME, CMake knows it is cross-compiling and sets CMAKE_CROSSCOMPILING to TRUE. That changes how it searches for libraries, headers and tools. They must come from the target, not the host.

A real toolchain file

The official docs’ own Linux and ARM example, lightly annotated:

arm-gcc.cmake
# Target platform. Linux, ARM architecture. set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) # Where the TARGET's root filesystem lives (headers and libs). set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs) # Where to install *on the host* while developing (optional). set(CMAKE_STAGING_PREFIX /home/devel/stage) # The cross compilers (they run on the host, target ARM). set(CMAKE_C_COMPILER /home/devel/gcc-4.7-linaro-rpi-gnueabihf/bin/arm-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER /home/devel/gcc-4.7-linaro-rpi-gnueabihf/bin/arm-linux-gnueabihf-g++) # find_package and find_library must look at the TARGET, not the host. set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # tools that RUN on host set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # libraries from target set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # headers from target set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # packages from target

Why

The three CMAKE_FIND_ROOT_PATH_MODE_* lines prevent the classic cross-compile bug. CMake finding your host’s libc or headers and mixing them into a target build. Tools that must run during the build, like code generators, come from the host (PROGRAM NEVER, never search the target for them). Everything a target links or includes must come from the target system.

What the variables mean

VariableMeaning
CMAKE_SYSTEM_NAMEThe target OS. Setting it is what tells CMake we are cross-compiling.
CMAKE_SYSTEM_PROCESSORTarget CPU, for example arm, aarch64.
CMAKE_<LANG>_COMPILERThe cross compiler, full path.
CMAKE_SYSROOTTarget root filesystem (headers and libs). Optional.
CMAKE_STAGING_PREFIXWhere to install on the host while developing.
CMAKE_FIND_ROOT_PATH_MODE_*Whether find_* commands search host, target, or both.

Clang, a special case

Clang is natively a cross compiler. Same binary, different target flags:

clang-arm.cmake
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER clang) set(CMAKE_CXX_COMPILER clang++) set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf) set(CMAKE_CXX_COMPILER_TARGET arm-linux-gnueabihf)

Using it, and how the project stays ignorant

# Your project's CMakeLists.txt does NOT change.
# Cross-compiling is a configure-time choice.
cmake -S . -B build-arm -DCMAKE_TOOLCHAIN_FILE=arm-gcc.cmake
cmake --build build-arm

Why

Toolchain files exist so your CMakeLists.txt never mentions “ARM” or “Raspberry Pi”. The same project cross-compiles to ARM, Android, the NDK provides its own toolchain file, and iOS, -DCMAKE_SYSTEM_NAME=iOS, with zero project changes. Platform knowledge lives in the toolchain file. The project just builds.

Android and iOS, the batteries-included cases

  • Android. Use the NDK’s toolchain file. -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake. CMake also supports the built-in variables, CMAKE_SYSTEM_NAME Android, CMAKE_ANDROID_NDK, CMAKE_ANDROID_ARCH_ABI.
  • iOS. The Xcode generator with -DCMAKE_SYSTEM_NAME=iOS is the official recommendation. The simulators and devices are SDK selections, and lesson 21’s multi-config thinking applies here.

Pitfalls when you are new to this

  • Runtime errors, not build errors. Cross-compiled code builds but does not run on your host. “Works on my machine” inverts. Test on the target, or in an emulator, or at least unit-test the pure logic on the host.
  • try_compile needs a target. For toolchains that cannot link host executables, set CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY so CMake’s internal checks do not fail on linking.
  • CMAKE_SOURCE_DIR inside a toolchain file is a trap (the docs warn about it). Toolchain files are loaded in different contexts. Use CMAKE_CURRENT_LIST_DIR for paths relative to the toolchain file.

Tip

message("${CMAKE_CROSSCOMPILING}") is a quick sanity check. TRUE means your toolchain file was picked up. When cross-compiling, CMake sets it automatically. If you expected TRUE and see FALSE, your -DCMAKE_TOOLCHAIN_FILE did not take effect. Check the path.

Read the docs