all
Stage 05

Cross-Compiling with CMake

Using CMake toolchain files for embedded cross-compilation: sysroot configuration, pkg-config integration, dependency management, and Yocto SDK integration.

11 min read
74851 chars

CMake Toolchain Files: The Basics

{:.gc-basic} Basic

CMake is a meta-build system that generates build files (Makefiles, Ninja files) from a high-level description. By default, CMake configures a build for the host machine — it finds the host’s gcc, the host’s /usr/lib, and generates code for the host architecture.

For cross-compilation, CMake needs to know:

  1. Which compiler to use (the cross-compiler)
  2. Which sysroot to search for headers and libraries (the target sysroot)
  3. What the target system name and processor are

All of this is provided through a toolchain file — a CMake script you pass with -DCMAKE_TOOLCHAIN_FILE=.

Minimal Toolchain File

# toolchain-arm-linux.cmake

# Tell CMake the target OS and CPU
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Specify the cross-compiler
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# Specify the sysroot (where target headers and libraries live)
set(CMAKE_SYSROOT /opt/rpi4-sysroot)

# Search for libraries and headers only in the sysroot, not on the host
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)

The four CMAKE_FIND_ROOT_PATH_MODE_* variables are critical — they control where CMake searches for programs, libraries, headers, and CMake packages:

Setting Description
NEVER Never search in sysroot — always use host paths (used for build tools like code generators)
ONLY Only search in sysroot — never use host paths (used for target libraries/headers)
BOTH Search sysroot first, then host paths (fallback)

Simple Hello World Cross-Compiled with CMake

Project structure:

hello/
├── CMakeLists.txt
├── toolchain-arm-linux.cmake
└── src/
    └── main.c
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hello C)

add_executable(hello src/main.c)

install(TARGETS hello DESTINATION bin)
# Configure: out-of-source build for the ARM target
$ mkdir build-arm && cd build-arm

$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/usr

$ cmake --build . -- -j$(nproc)

$ file hello
hello: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux-armhf.so.3,
for GNU/Linux 3.2.0, not stripped
# Build for host (native) in a separate directory
$ mkdir build-host && cd build-host
$ cmake .. -DCMAKE_BUILD_TYPE=Release
$ cmake --build .

$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), ...

This shows the power of out-of-source builds: the same source tree produces both ARM and host binaries by using different build directories.

CMAKE_FIND_ROOT_PATH Details

CMAKE_FIND_ROOT_PATH is a list of paths prepended to all find_* searches. When CMAKE_SYSROOT is set, CMake automatically adds it to CMAKE_FIND_ROOT_PATH. You can add additional paths:

# toolchain-arm-linux.cmake
set(CMAKE_SYSROOT /opt/rpi4-sysroot)

# Additional search paths (e.g., a staging directory with extra libraries)
list(APPEND CMAKE_FIND_ROOT_PATH /opt/rpi4-staging)

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_ROOT_PATH_MODE_PROGRAM NEVER)

With LIBRARY ONLY, find_library(SSL ssl) will search:

  • /opt/rpi4-sysroot/lib/
  • /opt/rpi4-sysroot/usr/lib/
  • /opt/rpi4-sysroot/lib/arm-linux-gnueabihf/
  • /opt/rpi4-staging/lib/
  • etc.

…but will not search /usr/lib (the host’s library directory).


Sysroot, Dependencies, and pkg-config Integration

{:.gc-mid} Intermediate

CMAKE_SYSROOT in the Toolchain File

Setting CMAKE_SYSROOT in the toolchain file has the same effect as passing --sysroot to every compiler invocation. CMake automatically adds the --sysroot flag:

# toolchain-arm-linux.cmake
set(CMAKE_SYSROOT /opt/rpi4-sysroot)

Verify it was applied by checking the compile commands:

$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

$ cat build-arm/compile_commands.json | python3 -m json.tool | grep -A5 "command"
{
  "command": "arm-linux-gnueabihf-gcc
    --sysroot=/opt/rpi4-sysroot
    -I/opt/rpi4-sysroot/usr/include
    -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard
    -o CMakeFiles/hello.dir/src/main.c.o
    -c /home/user/hello/src/main.c",
  "file": "/home/user/hello/src/main.c"
}

find_package for Cross-Compiled Dependencies

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(tls_client C)

# Find OpenSSL in the sysroot
find_package(OpenSSL REQUIRED)

add_executable(tls_client src/tls_client.c)

target_link_libraries(tls_client PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
)
$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DOPENSSL_ROOT_DIR=/opt/rpi4-sysroot/usr
-- Found OpenSSL: /opt/rpi4-sysroot/usr/lib/arm-linux-gnueabihf/libssl.so (found version "1.1.1n")
-- Configuring done
-- Build files have been written to: /home/user/tls_client/build-arm

If find_package cannot find the library automatically, provide the root directory hint:

$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_PREFIX_PATH=/opt/rpi4-sysroot/usr

pkg-config Integration in CMake

CMake’s FindPkgConfig module wraps pkg-config. For cross-compilation, you must point it to the correct pkg-config binary and search paths:

# In toolchain file or CMakeLists.txt:
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
set(ENV{PKG_CONFIG_LIBDIR}
    "${CMAKE_SYSROOT}/usr/lib/arm-linux-gnueabihf/pkgconfig:\
${CMAKE_SYSROOT}/usr/lib/pkgconfig:\
${CMAKE_SYSROOT}/usr/share/pkgconfig")

# Use the cross pkg-config if available
find_program(PKG_CONFIG_EXECUTABLE
    NAMES arm-linux-gnueabihf-pkg-config pkg-config
    PATHS /usr/bin
    NO_DEFAULT_PATH
)

In CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(myapp C)

find_package(PkgConfig REQUIRED)

# Find gpiod via pkg-config
pkg_check_modules(GPIOD REQUIRED libgpiod)

add_executable(myapp src/main.c)

target_include_directories(myapp PRIVATE ${GPIOD_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${GPIOD_LIBRARIES})
target_compile_options(myapp PRIVATE ${GPIOD_CFLAGS_OTHER})
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.2")
-- Checking for module 'libgpiod'
--   Found libgpiod, version 1.6.3
-- Configuring done

Building and Cross-Compiling a Project With libcurl

Complete example with external library dependency:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(http_client C)

set(CMAKE_C_STANDARD 11)

find_package(CURL REQUIRED)

add_executable(http_client
    src/main.c
    src/request.c
)

target_link_libraries(http_client PRIVATE CURL::libcurl)

install(TARGETS http_client
    RUNTIME DESTINATION bin
)
# toolchain-arm-linux.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_SYSROOT      /opt/rpi4-sysroot)

# Extra compiler flags for Cortex-A72 (RPi 4 uses A72, but 32-bit mode)
set(CMAKE_C_FLAGS_INIT   "-march=armv7-a -mfpu=vfpv4 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=vfpv4 -mfloat-abi=hard")

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)
$ mkdir build-arm && cd build-arm
$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/usr

$ cmake --build . -j$(nproc)
[ 33%] Building C object CMakeFiles/http_client.dir/src/main.c.o
[ 66%] Building C object CMakeFiles/http_client.dir/src/request.c.o
[100%] Linking C executable http_client
$ cmake --install . --prefix /opt/rpi4-staging
-- Install configuration: "Release"
-- Installing: /opt/rpi4-staging/bin/http_client

ExternalProject_Add for Cross-Compiling a Dependency

When a dependency is not available in the sysroot, build it from source using ExternalProject_Add:

include(ExternalProject)

ExternalProject_Add(
    libfoo_external
    URL         https://example.com/libfoo-1.2.tar.gz
    URL_HASH    SHA256=abc123...
    CMAKE_ARGS
        -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
        -DCMAKE_INSTALL_PREFIX=${CMAKE_SYSROOT}/usr
        -DCMAKE_BUILD_TYPE=Release
        -DBUILD_SHARED_LIBS=ON
    BUILD_ALWAYS OFF
)

# Make our target depend on the external project being built first
add_dependencies(myapp libfoo_external)

Advanced CMake: Qt Cross-Compilation, Presets, and Yocto Integration

{:.gc-adv} Advanced

Cross-Compiling Qt5 for Embedded Linux

Qt cross-compilation requires configuring Qt with device-specific settings:

# Step 1: Prepare the sysroot with Qt dependencies
$ rsync -avz pi@192.168.1.100:/usr/include/arm-linux-gnueabihf /opt/rpi-sysroot/usr/include/
$ rsync -avz pi@192.168.1.100:/usr/lib/arm-linux-gnueabihf    /opt/rpi-sysroot/usr/lib/

# Step 2: Configure Qt for cross-compilation
$ cd qt5-src
$ ./configure \
    -release \
    -opengl es2 \
    -device linux-rasp-pi4-v3d-g++ \
    -device-option CROSS_COMPILE=arm-linux-gnueabihf- \
    -sysroot /opt/rpi-sysroot \
    -prefix /usr/local/qt5 \
    -extprefix /opt/qt5-arm \
    -hostprefix /opt/qt5-host \
    -no-use-gold-linker \
    -v

$ make -j$(nproc)
$ make install

In a CMake project that uses cross-compiled Qt:

# toolchain-arm-qt.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_SYSROOT      /opt/rpi-sysroot)

# Point CMake to the cross-compiled Qt installation
set(CMAKE_PREFIX_PATH  /opt/qt5-arm)

# Point to host Qt tools (moc, uic, rcc run on the build machine)
set(QT_HOST_PATH       /opt/qt5-host)

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)
# CMakeLists.txt for a Qt project
cmake_minimum_required(VERSION 3.16)
project(myqtapp CXX)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt5 REQUIRED COMPONENTS Core Widgets Quick)

add_executable(myqtapp
    main.cpp
    mainwindow.cpp
    mainwindow.h
    resources.qrc
)

target_link_libraries(myqtapp PRIVATE
    Qt5::Core Qt5::Widgets Qt5::Quick
)
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-qt.cmake
$ cmake --build . -j$(nproc)

Generating compile_commands.json for Cross-Compile IDE Support

compile_commands.json is a compilation database that enables clangd, VS Code IntelliSense, and other tools to understand your cross-compiled project:

$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

$ ls build-arm/
CMakeCache.txt  CMakeFiles/  compile_commands.json  Makefile
# Symlink to project root so editors/clangd find it
$ ln -s build-arm/compile_commands.json compile_commands.json

The compile_commands.json contains the exact compiler invocations including all flags, include paths, and defines — allowing clangd to provide accurate cross-platform code completion and error checking.

Configure clangd to use the cross-compiler’s headers by adding .clangd to your project root:

# .clangd
CompileFlags:
  Add:
    - --sysroot=/opt/rpi4-sysroot
    - --target=arm-linux-gnueabihf

Multi-Stage Builds: Host Tools + Target Code

Some projects need to build code generation tools natively (to run on the host) and the actual application code for the target. CMake handles this with separate configure stages:

# Top-level CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(embedded_app)

# Check if we're doing a host build or cross build
if(CMAKE_CROSSCOMPILING)
    # Cross-compilation: build the target application
    add_subdirectory(src)
    add_subdirectory(target_libs)
else()
    # Native build: build host tools only
    add_subdirectory(tools)
endif()

Build script:

#!/bin/bash
# build.sh

# Step 1: Build native host tools
mkdir -p build-host && cd build-host
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -- -j$(nproc)
cd ..

# Step 2: Cross-compile target application
# Host tools are now in build-host/tools/
mkdir -p build-arm && cd build-arm
cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm-linux.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DHOST_TOOLS_DIR=$(realpath ../build-host/tools)
cmake --build . -- -j$(nproc)

CMake Preset Files

CMake 3.19+ supports preset files for managing multiple build configurations:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "host-debug",
      "displayName": "Host Debug Build",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build-host-debug",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug"
      }
    },
    {
      "name": "arm-release",
      "displayName": "ARM Release Build",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build-arm-release",
      "toolchainFile": "${sourceDir}/cmake/toolchain-arm-linux.cmake",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_INSTALL_PREFIX": "/usr"
      }
    },
    {
      "name": "aarch64-release",
      "displayName": "AArch64 Release Build",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build-aarch64-release",
      "toolchainFile": "${sourceDir}/cmake/toolchain-aarch64-linux.cmake",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "arm-release",
      "configurePreset": "arm-release",
      "jobs": 8
    }
  ]
}
# List available presets
$ cmake --list-presets
Available configure presets:

  "host-debug"       - Host Debug Build
  "arm-release"      - ARM Release Build
  "aarch64-release"  - AArch64 Release Build
# Configure and build using the ARM preset
$ cmake --preset arm-release
$ cmake --build --preset arm-release

Yocto SDK Integration

When using a Yocto-built SDK, the SDK ships an environment setup script that configures all cross-compilation variables. You can generate a CMake toolchain file from it:

# Install the SDK (creates /opt/poky/5.0/)
$ ./poky-glibc-x86_64-core-image-minimal-cortexa9t2hf-neon-toolchain-5.0.sh
Poky (Yocto Project Reference Distro) SDK installer version 5.0
================================================================
Enter target directory for SDK (default: /opt/poky/5.0):
Extracting SDK...............done
Setting it up...done
SDK has been successfully set up and is ready to be used.
# Source the environment script
$ source /opt/poky/5.0/environment-setup-cortexa9t2hf-neon-poky-linux-gnueabi

# Verify the environment is set
$ echo $CC
arm-poky-linux-gnueabi-gcc  -march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard --sysroot=/opt/poky/5.0/sysroots/cortexa9t2hf-neon-poky-linux-gnueabi
$ echo $SDKTARGETSYSROOT
/opt/poky/5.0/sysroots/cortexa9t2hf-neon-poky-linux-gnueabi

Generate a CMake toolchain file from the SDK environment:

# Generate toolchain file from Yocto environment variables
cat > yocto-sdk-toolchain.cmake << EOF
# Auto-generated from Yocto SDK environment
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(SDK_SYSROOT "$ENV{SDKTARGETSYSROOT}")

set(CMAKE_C_COMPILER   "$ENV{CC}")
set(CMAKE_CXX_COMPILER "$ENV{CXX}")
set(CMAKE_LINKER       "$ENV{LD}")
set(CMAKE_AR           "$ENV{AR}")
set(CMAKE_RANLIB       "$ENV{RANLIB}")
set(CMAKE_STRIP        "$ENV{STRIP}")

set(CMAKE_SYSROOT ${SDK_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${SDK_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)

# pkg-config for cross-compilation
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${SDK_SYSROOT})
set(ENV{PKG_CONFIG_LIBDIR}
    "${SDK_SYSROOT}/usr/lib/pkgconfig:${SDK_SYSROOT}/usr/share/pkgconfig")
EOF
$ cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=./yocto-sdk-toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release

$ cmake --build . -j$(nproc)

$ file myapp
myapp: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux-gnueabi.so.3,
for GNU/Linux 3.2.0, not stripped

Testing Cross-Compiled Binaries with QEMU via ctest

# CMakeLists.txt
enable_testing()

# Find qemu-arm for test execution
find_program(QEMU_ARM qemu-arm)

add_test(
    NAME run_unit_tests
    COMMAND ${QEMU_ARM}
        -L /opt/rpi4-sysroot
        $<TARGET_FILE:myapp_tests>
        --gtest_output=xml:test_results.xml
)

set_tests_properties(run_unit_tests PROPERTIES
    TIMEOUT 30
    ENVIRONMENT "QEMU_LD_PREFIX=/opt/rpi4-sysroot"
)
$ ctest --test-dir build-arm -V
UpdateCTestConfiguration  from :/home/user/project/build-arm/DartConfiguration.tcl
Test project /home/user/project/build-arm
    Start 1: run_unit_tests
1/1 Test #1: run_unit_tests .....................   Passed    0.84 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.85 sec

This enables CI pipelines to run ARM unit tests without physical hardware by using QEMU user-mode emulation.


Interview Questions

{:.gc-iq} Interview Q&A

Q: What is a CMake toolchain file and what must it contain?

A CMake toolchain file is a CMake script passed to cmake with -DCMAKE_TOOLCHAIN_FILE=path/to/toolchain.cmake. CMake reads it before configuring the project, using it to configure the cross-compilation environment. At minimum it must contain: CMAKE_SYSTEM_NAME (e.g., Linux) to tell CMake the target OS, CMAKE_SYSTEM_PROCESSOR (e.g., arm) to set the target CPU, CMAKE_C_COMPILER pointing to the cross-compiler (e.g., arm-linux-gnueabihf-gcc), and the four CMAKE_FIND_ROOT_PATH_MODE_* variables to control where CMake searches for libraries, headers, and programs. CMAKE_SYSROOT is also strongly recommended to direct all library and header searches to the target sysroot instead of the host filesystem. Without proper FIND_ROOT_PATH_MODE settings, CMake may find the host’s x86-64 libraries instead of the target’s ARM libraries.

Q: How do you tell CMake to search for libraries in the sysroot instead of the host?

Set CMAKE_SYSROOT to the sysroot path in the toolchain file — this adds the sysroot to CMAKE_FIND_ROOT_PATH and passes --sysroot to every compiler invocation. Then set CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY (and similarly for INCLUDE and PACKAGE) to restrict library searches to the sysroot. With ONLY, find_library(SSL ssl) will only search paths within the sysroot like /sysroot/usr/lib and /sysroot/lib, never the host’s /usr/lib. Additionally, set CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER so that build tools like code generators run from the host PATH, not the sysroot.

Q: What does CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY mean?

It means that all find_library() calls will only search within paths that are prefixed by CMAKE_FIND_ROOT_PATH (which includes the sysroot). The host system library paths (/usr/lib, /lib, etc.) are never searched. This is the correct setting for cross-compilation because you want to link against the target architecture’s libraries (ARM), not the host’s libraries (x86-64). If this were set to BOTH, CMake would search the sysroot first and fall back to host paths — which could accidentally find an x86-64 library if the ARM version is not in the sysroot, causing a silent ABI mismatch that produces a broken binary.

Q: How do you cross-compile a project that uses pkg-config dependencies?

Set three environment variables in the toolchain file or before running cmake: PKG_CONFIG_SYSROOT_DIR to the sysroot path (pkg-config prepends this to all paths it returns), PKG_CONFIG_LIBDIR to a colon-separated list of directories inside the sysroot where .pc files are located (e.g., ${SYSROOT}/usr/lib/arm-linux-gnueabihf/pkgconfig:${SYSROOT}/usr/share/pkgconfig), and unset PKG_CONFIG_PATH to prevent host paths from bleeding in. In CMakeLists.txt, use find_package(PkgConfig) then pkg_check_modules(LIBNAME REQUIRED libname). Apply the results with target_include_directories(myapp PRIVATE ${LIBNAME_INCLUDE_DIRS}) and target_link_libraries(myapp PRIVATE ${LIBNAME_LIBRARIES}). The PKG_CONFIG_SYSROOT_DIR variable ensures that paths returned by pkg-config (e.g., -I/usr/include) are automatically prefixed with the sysroot.

Q: How do you integrate a Yocto SDK with a CMake project?

First source the SDK’s environment-setup script: source /opt/poky/VERSION/environment-setup-MACHINE-...-linux. This sets environment variables including CC, CXX, LD, SDKTARGETSYSROOT, PKG_CONFIG_SYSROOT_DIR, etc. Then either: (1) use the cmake wrapper the SDK provides — it automatically injects the toolchain settings, or (2) write a CMake toolchain file that reads these environment variables using $ENV{CC}, $ENV{SDKTARGETSYSROOT}, etc. The generated toolchain file sets CMAKE_C_COMPILER, CMAKE_SYSROOT, CMAKE_FIND_ROOT_PATH, and pkg-config variables from the SDK environment. Run cmake with this toolchain file to produce ARM binaries that are compatible with the Yocto-built rootfs — same glibc version, same library versions, same kernel headers.


References

{:.gc-ref} References