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’slibcor 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
| Variable | Meaning |
|---|---|
CMAKE_SYSTEM_NAME | The target OS. Setting it is what tells CMake we are cross-compiling. |
CMAKE_SYSTEM_PROCESSOR | Target CPU, for example arm, aarch64. |
CMAKE_<LANG>_COMPILER | The cross compiler, full path. |
CMAKE_SYSROOT | Target root filesystem (headers and libs). Optional. |
CMAKE_STAGING_PREFIX | Where 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.cmakeset(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.txtnever 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=iOSis 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_LIBRARYso CMake’s internal checks do not fail on linking. CMAKE_SOURCE_DIRinside a toolchain file is a trap (the docs warn about it). Toolchain files are loaded in different contexts. UseCMAKE_CURRENT_LIST_DIRfor paths relative to the toolchain file.
Tip
message("${CMAKE_CROSSCOMPILING}")is a quick sanity check.TRUEmeans your toolchain file was picked up. When cross-compiling, CMake sets it automatically. If you expectedTRUEand seeFALSE, your-DCMAKE_TOOLCHAIN_FILEdid not take effect. Check the path.
Read the docs