AArch64 Cross-Compile with CMake: How to Use a Toolchain File
For cross-compiling to a target board like aarch64, point CMAKE_TOOLCHAIN_FILE at a file that sets the target system and compilers, set CMAKE_SYSROOT to the target root filesystem, and use the four CMAKE_FIND_ROOT_PATH_MODE_* variables to stop find_package() and friends from grabbing host libraries.
This is a general overview based on the official CMake documentation (the cmake-toolchains manual and related variable pages). Swap in the actual toolchain and SDK paths for whichever cross-compiler distribution you’re using.
What Goes Into a Toolchain File?
One-line answer: Set CMAKE_SYSTEM_NAME to switch on cross-compiling mode, and set CMAKE_SYSTEM_PROCESSOR plus CMAKE_C_COMPILER/CMAKE_CXX_COMPILER to name the target architecture and compilers.
By default, CMake configures a buildsystem for the host machine that’s actually running the build. To cross-compile, you point the CMAKE_TOOLCHAIN_FILE cache variable at a separate .cmake file, which pre-fills these values early in the configure step.
Setting CMAKE_SYSTEM_NAME to the target OS (for example, Linux) is enough on its own to put CMake into cross-compiling mode, as long as it differs from the host system name. Add CMAKE_SYSTEM_PROCESSOR for the target architecture (aarch64), and point CMAKE_C_COMPILER and CMAKE_CXX_COMPILER at the cross compilers themselves, such as aarch64-linux-gnu-gcc and aarch64-linux-gnu-g++.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
With just these four lines, CMake recognizes that the current build target is aarch64, not the host, and switches its compiler and library search logic into cross-compiling mode.
How Do You Set Up the Sysroot?
One-line answer: Set CMAKE_SYSROOT to the target root filesystem path, and CMake will automatically pass --sysroot=<path> to the compiler and linker.
The cross compiler itself runs on the host machine, but its headers and libraries need to come from the target environment. CMAKE_SYSROOT is the variable that tells CMake where that target root filesystem lives.
set(CMAKE_SYSROOT /opt/aarch64-sysroot)
Once this is set, CMake automatically appends --sysroot=/opt/aarch64-sysroot whenever it invokes the compiler or linker. As a result, the compiler looks inside the specified sysroot’s include and lib directories first, instead of the host’s default /usr/include and /usr/lib.
In practice, the sysroot is either copied from the actual target board or is the root filesystem directory shipped with your cross-compilation SDK. If the path is wrong, it usually shows up quickly as a missing-header error, so it’s fairly easy to catch.
How Do You Stop find_package() From Grabbing Host Libraries?
One-line answer: Set CMAKE_FIND_ROOT_PATH_MODE_PROGRAM to NEVER, and set LIBRARY, INCLUDE, and PACKAGE to ONLY to fully separate the two search scopes.
The most common cross-compiling headache is find_package() or find_library() picking up libraries from the host system (say, x86_64) instead of the sysroot. The fix is to split search behavior cleanly using the four CMAKE_FIND_ROOT_PATH_MODE_* variables.
Programs and libraries need different search scopes
Anything that must actually run during the build — like a code generator — needs to be a host binary, so CMAKE_FIND_ROOT_PATH_MODE_PROGRAM should stay NEVER, restricting that search to host paths only. Libraries, headers, and package configs meant for linking, on the other hand, should only ever come from the target sysroot, so the other three variables should be ONLY, which excludes anything outside the sysroot entirely.
set(CMAKE_FIND_ROOT_PATH /opt/aarch64-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)
With this in place, commands like find_package(), find_library(), and find_path() only look for libraries, headers, and package config files inside the sysroot named by CMAKE_FIND_ROOT_PATH, and skip the host system’s /usr/lib or /usr/include entirely.
VariableRecommended valueReason
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM``NEVERPrograms run during the build need to run on the host
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY``ONLYLibraries for linking must come from the target sysroot only
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE``ONLYHeaders must also be resolved against the target sysroot only
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE``ONLYPackage config files should not bleed in from the host either
Skip these four variables, and a build can still succeed while quietly linking host-side libraries — a problem that only surfaces once you try to actually run the binary on the target board. It’s safer to bake this setup into the toolchain file from the start.
FAQ
Where does the toolchain file go, and how do you point CMake at it?
The .cmake file itself can live anywhere you like, inside or outside the project. You point CMake at it during configuration with a cache variable, like cmake -S <source> -B <build> -DCMAKE_TOOLCHAIN_FILE=/path/to/aarch64-toolchain.cmake.
Can you skip the sysroot and just point at the compiler?
It might work, but it isn’t recommended. Without a sysroot, the compiler falls back on its default search paths (the host’s), leaving room for header and library versions to get mixed up regardless of how the CMAKE_FIND_ROOT_PATH_MODE_* variables are set.
Why does only CMAKE_FIND_ROOT_PATH_MODE_PROGRAM stay at NEVER?
Because anything executed mid-build, like a code generator, needs to be a binary that actually runs on the host — not an aarch64 target binary. Setting this to ONLY instead can break the build by making CMake hunt for a target-only executable that the host can’t run.
Wrapping Up
An aarch64 cross-compiling toolchain file really boils down to three jobs: flip on cross-compiling mode with CMAKE_SYSTEM_NAME, CMAKE_SYSTEM_PROCESSOR, and the compiler paths; point CMAKE_SYSROOT at the target root filesystem; and use the four CMAKE_FIND_ROOT_PATH_MODE_* variables to separate program lookup from library/header/package lookup. Get these three pieces right, and you’ve headed off most cases of accidentally linking host libraries.
Sources
One-line answer: The variables and behavior described here were checked against the official CMake documentation — the cmake-toolchains manual and its related variable pages.
- cmake-toolchains(7) manual — Cross Compiling — used to check
CMAKE_SYSTEM_NAME,CMAKE_SYSTEM_PROCESSOR, and the conditions that trigger cross-compiling mode. - CMAKE_SYSROOT variable documentation — used to check that setting the sysroot automatically passes the
--sysrootflag. - CMAKE_FIND_ROOT_PATH_MODE_PROGRAM variable documentation — used to check the default and meaning of
ONLY/NEVERfor thePROGRAM/LIBRARY/INCLUDE/PACKAGEmodes.