요약
aarch64 등 타깃 보드용으로 크로스 컴파일할 때는 CMAKE_TOOLCHAIN_FILE로 타깃 시스템 정보와 컴파일러를 지정하고, CMAKE_SYSROOT로 타깃 루트 파일 시스템을 잡은 뒤, CMAKE_FIND_ROOT_PATH_MODE_* 변수로 find_package() 등이 호스트 라이브러리를 잘못 집어오는 문제를 막습니다.
이 글은 CMake 공식 문서(cmake-toolchains 매뉴얼 및 관련 변수 문서)를 기준으로 정리한 일반 설명이며, 실제 툴체인·SDK 경로는 사용 중인 크로스 컴파일러 배포판에 맞게 바꿔야 합니다.
toolchain 파일에 무엇을 넣나?
한 줄 답: CMAKE_SYSTEM_NAME으로 크로스 컴파일 모드를 켜고, CMAKE_SYSTEM_PROCESSOR와 CMAKE_C_COMPILER/CMAKE_CXX_COMPILER로 타깃 아키텍처와 컴파일러를 지정합니다.
CMake는 기본적으로 지금 빌드가 실행되는 호스트 머신을 대상으로 빌드시스템을 구성합니다. 크로스 컴파일이 필요하면 CMAKE_TOOLCHAIN_FILE 캐시 변수로 별도의 .cmake 파일을 가리켜 설정 단계 초반에 이 값들을 미리 채워 넣어야 합니다.
CMAKE_SYSTEM_NAME에 타깃 운영체제 이름(예: Linux)을 넣으면, 이 값이 호스트 시스템 이름과 다르다는 것만으로 CMake는 크로스 컴파일 모드로 동작합니다. 여기에 CMAKE_SYSTEM_PROCESSOR로 타깃 아키텍처(aarch64)를 지정하고, CMAKE_C_COMPILER·CMAKE_CXX_COMPILER에 각각 aarch64-linux-gnu-gcc, 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++)
이 4줄만으로 CMake는 "지금 만들 결과물은 호스트가 아니라 aarch64 타깃용"이라는 사실을 인식하고, 이후 컴파일러 탐색이나 라이브러리 탐색 로직을 크로스 컴파일 방식으로 바꿉니다.
sysroot는 어떻게 맞추나?
한 줄 답: CMAKE_SYSROOT에 타깃 루트 파일 시스템 경로를 지정하면, CMake가 컴파일러·링커에 --sysroot=<path> 플래그를 자동으로 넘깁니다.
크로스 컴파일러 자체는 호스트 머신에서 실행되지만, 헤더와 라이브러리는 타깃 환경 것을 써야 합니다. CMAKE_SYSROOT는 이 타깃 루트 파일 시스템의 위치를 CMake에게 알려주는 변수입니다.
set(CMAKE_SYSROOT /opt/aarch64-sysroot)
이 값을 설정하면 CMake는 컴파일러와 링커를 호출할 때 --sysroot=/opt/aarch64-sysroot를 자동으로 붙여줍니다. 그 결과 컴파일러는 기본 경로인 호스트의 /usr/include, /usr/lib 대신 지정한 sysroot 안의 include·lib를 먼저 찾게 됩니다.
sysroot는 실제로 타깃 보드에서 복사해 오거나, 사용 중인 크로스 컴파일 SDK가 함께 제공하는 루트 파일 시스템 디렉터리를 그대로 가리키면 됩니다. 경로가 잘못되면 헤더를 못 찾는 오류로 바로 드러나므로 확인이 비교적 쉽습니다.
find_package가 호스트 라이브러리를 집을 때 어떻게 막나?
한 줄 답: CMAKE_FIND_ROOT_PATH_MODE_PROGRAM은 NEVER로, LIBRARY·INCLUDE·PACKAGE는 ONLY로 설정해 검색 범위를 완전히 분리합니다.
크로스 컴파일에서 가장 흔히 겪는 문제는 find_package()나 find_library()가 sysroot가 아니라 호스트 시스템(예: x86_64) 경로에서 라이브러리를 찾아버리는 현상입니다. 이 문제는 CMAKE_FIND_ROOT_PATH_MODE_* 변수 네 가지로 검색 대상을 명확히 나눠서 막습니다.
실행 파일과 라이브러리는 검색 범위가 달라야 합니다
빌드 중에 실행해야 하는 코드 생성기 같은 프로그램은 호스트에서 돌아가야 하므로, CMAKE_FIND_ROOT_PATH_MODE_PROGRAM은 NEVER로 두어 호스트 경로에서만 찾도록 합니다. 반대로 링크에 쓸 라이브러리·헤더·패키지 설정은 타깃 sysroot 안에만 있어야 하므로, 나머지 세 변수는 ONLY로 지정해 sysroot 바깥은 아예 보지 않게 만듭니다.
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)
이렇게 설정해 두면 find_package(), find_library(), find_path() 같은 명령이 CMAKE_FIND_ROOT_PATH로 지정한 sysroot 안에서만 라이브러리·헤더·패키지 설정 파일을 찾고, 호스트 시스템의 /usr/lib나 /usr/include는 건너뜁니다.
| 변수 | 권장 값 | 이유 |
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM | NEVER | 빌드 중 실행할 프로그램은 호스트에서 실행되어야 합니다. |
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY | ONLY | 링크용 라이브러리는 타깃 sysroot에만 있어야 합니다. |
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE | ONLY | 헤더도 타깃 sysroot 기준으로만 찾아야 합니다. |
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE | ONLY | 패키지 설정 파일도 호스트 경로를 침범하지 않아야 합니다. |
이 네 변수를 빼먹으면, 빌드는 되지만 실제로는 호스트용 라이브러리가 링크되어 타깃 보드에서 실행할 때만 문제가 드러나는 경우가 있습니다. 크로스 컴파일 toolchain 파일에서는 처음부터 이 설정을 넣어 두는 것이 안전합니다.
FAQ
toolchain 파일은 어디에 두고 어떻게 지정합니까?
파일 자체는 프로젝트 안이든 밖이든 원하는 위치에 .cmake 파일로 두면 되고, 설정 단계에서 cmake -S <source> -B <build> -DCMAKE_TOOLCHAIN_FILE=/path/to/aarch64-toolchain.cmake처럼 캐시 변수로 지정합니다.
sysroot 없이 그냥 컴파일러 경로만 지정해도 됩니까?
동작할 수는 있지만 권장하지 않습니다. sysroot를 지정하지 않으면 컴파일러가 헤더나 라이브러리를 찾을 때 기본 검색 경로(호스트 경로)에 의존하게 되어, 위 CMAKE_FIND_ROOT_PATH_MODE_* 설정과 별개로 헤더·라이브러리 버전이 뒤섞일 위험이 남습니다.
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM만 NEVER로 두는 이유는 무엇입니까?
빌드 중간에 실행되는 코드 생성기 같은 프로그램은 타깃 아키텍처(aarch64) 바이너리가 아니라 호스트에서 바로 실행 가능한 바이너리여야 하기 때문입니다. 이 값을 ONLY로 두면 오히려 호스트에서 실행 불가능한 타깃용 실행 파일을 찾다가 빌드가 깨질 수 있습니다.
정리하며
aarch64 크로스 컴파일 toolchain 파일은 결국 세 가지 역할로 요약됩니다. CMAKE_SYSTEM_NAME·CMAKE_SYSTEM_PROCESSOR·컴파일러 경로로 크로스 컴파일 모드를 켜고, CMAKE_SYSROOT로 타깃 루트 파일 시스템을 잡고, CMAKE_FIND_ROOT_PATH_MODE_* 네 변수로 프로그램 탐색과 라이브러리·헤더·패키지 탐색의 범위를 분리하는 것입니다. 이 세 부분만 정확히 채워 두면 호스트 라이브러리가 실수로 링크되는 문제를 대부분 예방할 수 있습니다.
출처
한 줄 답: 본문의 변수와 동작 방식은 CMake 공식 문서(cmake-toolchains 매뉴얼과 관련 변수 문서)를 기준으로 확인했습니다.
TL;DR
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.
| Variable | Recommended value | Reason |
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM | NEVER | Programs run during the build need to run on the host |
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY | ONLY | Libraries for linking must come from the target sysroot only |
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE | ONLY | Headers must also be resolved against the target sysroot only |
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE | ONLY | Package 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.