Skip to content

CMake Build System Guide — Complete Cross-Platform C and C++ Builds

DodaTech Updated 2026-06-23 5 min read

In this tutorial, you'll learn about CMake Build System Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

CMake is a cross-platform build system generator that produces native build files (Makefiles, Ninja, Visual Studio solutions) from a declarative CMakeLists.txt configuration, enabling portable C and C++ project builds across all major platforms.

What You'll Learn

You'll learn modern CMake with target-based configuration, how to use generators, integrate external libraries with find_package, write custom toolchain files, and structure multi-directory projects for maintainability.

Why CMake Matters

C and C++ projects must compile on Windows, Linux, and macOS with different compilers and build systems. CMake abstracts these differences, providing a single build description that generates platform-native build files. It is used by LLVM, Qt, OpenCV, and thousands of open-source projects.

Real-World Use

DodaZIP uses CMake to build its compression library across Windows (MSVC), Linux (GCC), and macOS (Clang), with NEON and SSE4.2 dispatching selected at CMake time based on target architecture detection.

Prerequisites

  • C or C++ experience
  • Basic Makefile knowledge
  • CMake 3.27+ installed

Step 1: Basic CMakeLists.txt

The CMakeLists.txt file is the entry point. It specifies the minimum CMake version, project name, language, and executable target.

cmake_minimum_required(VERSION 3.20)
project(HelloWorld C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

add_executable(hello main.c utils.c utils.h)

Then build with:

mkdir build && cd build
cmake ..
cmake --build .
./hello

Expected output: CMake generates build files in build/, then compiles and links the hello executable. Running it executes the program.

Step 2: Modern CMake with Target-Based Design

Modern CMake treats libraries and executables as targets with their own properties — include directories, compile definitions, and link libraries propagate through the dependency graph automatically.

cmake_minimum_required(VERSION 3.20)
project(Calculator LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# A library target with its own include directory
add_library(calc_lib STATIC
    src/add.cpp
    src/subtract.cpp
    src/multiply.cpp
)

# PUBLIC means consumers also get these includes
target_include_directories(calc_lib PUBLIC include)

# An executable that links to the library
add_executable(calc_app src/main.cpp)

# Link libraries propagates all PUBLIC properties
target_link_libraries(calc_app PRIVATE calc_lib)

Expected behavior: When calc_app links calc_lib with PRIVATE, it inherits the include directories marked PUBLIC on calc_lib. This Encapsulation prevents leaked transitive dependencies.

Step 3: Finding and Using External Libraries

The find_package command locates installed libraries and imports their targets.

cmake_minimum_required(VERSION 3.20)
project(ImageProcessor CXX)

set(CMAKE_CXX_STANDARD 17)

find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)
find_package(fmt CONFIG REQUIRED)

add_executable(processor src/main.cpp)

target_link_libraries(processor PRIVATE
    OpenCV::core
    OpenCV::imgproc
    OpenCV::highgui
    fmt::fmt
)

target_compile_features(processor PUBLIC cxx_std_17)
# Configure and build with OpenCV
cmake -B build -DCMAKE_PREFIX_PATH=/usr/local/opencv
cmake --build build

Expected behavior: CMake searches standard paths for OpenCV and fmt config files. If found, it imports their targets with all include directories and dependencies automatically configured.

Step 4: Toolchain Files for Cross-Compilation

Toolchain files tell CMake which compiler, sysroot, and flags to use when building for a different target platform.

# toolchain-arm.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 /usr/arm-linux-gnueabihf)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# Use the toolchain file for cross-compilation
cmake -B build-arm -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake
cmake --build build-arm
# Output binaries are ARM ELF executables
file build-arm/processor
# processor: ELF 32-bit LSB executable, ARM

Expected behavior: The generated build files use the ARM cross-compiler instead of the system compiler, and the output binary targets the ARM architecture.

Architecture

flowchart LR
    subgraph "Source"
        CM[CMakeLists.txt]
        SRC[C/C++ Sources]
        TOOL[Toolchain File]
    end
    subgraph "CMake Generation"
        CONF[Configure Step]
        GEN[Generate Step]
    end
    subgraph "Build System"
        MK[Makefile / Ninja]
        VS[Visual Studio Solution]
        XC[Xcode Project]
    end
    subgraph "Build"
        COMP[Compiler]
        LINK[Linker]
        BIN[Binary]
    end
    CM --> CONF
    SRC --> CONF
    TOOL --> CONF
    CONF --> GEN
    GEN --> MK
    GEN --> VS
    GEN --> XC
    MK --> COMP
    COMP --> LINK
    LINK --> BIN

Common Errors

1. Could not find a package configuration file CMake cannot locate the required library. Install the library with its CMake config files, or provide -DCMAKE_PREFIX_PATH pointing to the installation prefix.

2. Target "example" links to target "Boost::boost" but the target was not found The library was either not found or the CMake config does not define imported targets. Fall back to FindBoost module or specify find_package with correct components.

3. The compiler is not able to compile a simple test program This indicates a broken toolchain — wrong compiler path, missing sysroot, or incompatible flags. Verify the compiler works standalone: arm-linux-gnueabihf-gcc --version.

4. Policy CMPXXXX is not set CMake policies control backward compatibility. Set policies explicitly: cmake_policy(SET CMP0074 NEW) to suppress warnings and ensure consistent behavior.

5. INTERFACE_LIBRARY targets cannot be built directly Header-only libraries use add_library(lib INTERFACE). They cannot be compiled — they only propagate usage requirements to consumers.

Practice Questions

1. What is the difference between PRIVATE, PUBLIC, and INTERFACE in target properties? PRIVATE applies only to the target itself. INTERFACE applies only to consumers. PUBLIC applies to both. This controls how usage requirements propagate through the dependency graph.

2. Why does CMake separate configure and build steps? The configure step analyzes the system, finds libraries, and generates build files. The build step executes compilation. Separation allows reconfiguration without rebuilding everything.

3. What is a generator in CMake? A generator produces build files for a specific tool — Unix Makefiles, Ninja, Visual Studio, Xcode. Choose with -G flag: cmake -G "Ninja" ...

4. Challenge: Header-only library Create a CMake target for a header-only library using INTERFACE. Ensure consumers automatically get the include directory and any required compile definitions.

5. Challenge: Install and export Extend a CMake project to install the library and generate a CMake package config so other projects can use find_package to consume it.

FAQ

How does CMake differ from Make?

Make runs build rules directly. CMake generates build files for Make, Ninja, or IDE-native formats. CMake is higher-level and platform-aware, while Make operates at the shell-command level.

Can CMake build for Embedded Systems?

Yes. CMake supports toolchain files, custom linkers, and sysroots needed for embedded development. It is widely used with ARM GCC, IAR, and Keil toolchains.

What is Modern CMake?

Modern CMake (3.x) uses target-based design with properties instead of global variables. It encourages target_include_directories, target_link_libraries, and target_compile_definitions instead of older directory-level commands.

Next Steps

  • Explore Makefiles to understand what CMake generates under the hood
  • Learn C++ build optimization with profile-guided optimization in CMake
  • Study the Docker build strategies tutorial for containerizing CMake-based projects
  • Review the cross-compilation guide for targeting ARM and RISC-V platforms

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro