Build Systems
CMake Basics
CMake is the industry-standard build system generator used by ESP-IDF, STM32 HAL, Qt, OpenCV, TensorFlow, and thousands of professional projects. You write one clean CMakeLists.txt and CMake generates the correct build files for any OS or compiler.
CMake vs Make — The Key Difference
| Make | CMake | |
|---|---|---|
| What you write | Makefile with compiler flags | CMakeLists.txt with high-level descriptions |
| What runs the compiler | Make itself | Make or Ninja (CMake generates their files) |
| Cross-platform | No — separate Makefiles per OS | Yes — one CMakeLists.txt works everywhere |
| Cross-compile (MCU) | Manual flags | Toolchain files (-DCMAKE_TOOLCHAIN_FILE=...) |
| Used by | Old projects | ESP-IDF, STM32, Qt, OpenCV, TFLite |
Example 1 — Single File (Desktop)
1cmake_minimum_required(VERSION 3.16)2project(HelloWorld C)34add_executable(hello main.c)mkdir build && cd build
cmake ..
cmake --build .
./hello
Example 2 — Multi-File with Library (Desktop)
project/
├── CMakeLists.txt
├── main.c
├── include/
│ └── sensor.h
└── src/
└── sensor.c
1cmake_minimum_required(VERSION 3.16)2project(SensorApp C)34# Create a static library from sensor.c5add_library(sensor_lib STATIC src/sensor.c)67# Expose the include/ directory to anything that links sensor_lib8target_include_directories(sensor_lib PUBLIC include/)910# Set compile options just for this library11target_compile_options(sensor_lib PRIVATE -Wall -O2)1213# Create the main executable14add_executable(sensor_app main.c)1516# Link the library to the executable17target_link_libraries(sensor_app PRIVATE sensor_lib)mkdir build && cd build
cmake ..
cmake --build . --parallel $(nproc)
Example 3 — Multi-Directory Project
project/
├── CMakeLists.txt ← Top-level
├── main.c
├── drivers/
│ ├── CMakeLists.txt ← Sub-project
│ ├── uart.c
│ └── uart.h
└── hal/
├── CMakeLists.txt
├── gpio.c
└── gpio.h
Top-level CMakeLists.txt:
1cmake_minimum_required(VERSION 3.16)2project(EmbeddedApp C)34# Add subdirectories — CMake will read their CMakeLists.txt5add_subdirectory(drivers)6add_subdirectory(hal)78add_executable(firmware main.c)9target_link_libraries(firmware PRIVATE drivers hal)drivers/CMakeLists.txt:
1add_library(drivers STATIC uart.c)2target_include_directories(drivers PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})hal/CMakeLists.txt:
1add_library(hal STATIC gpio.c)2target_include_directories(hal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})Example 4 — STM32 Cross-Compilation with CMake
CMake handles cross-compilation cleanly through toolchain files. The toolchain file tells CMake to use arm-none-eabi-gcc instead of the host gcc.
arm-none-eabi-toolchain.cmake:
1set(CMAKE_SYSTEM_NAME Generic)2set(CMAKE_SYSTEM_PROCESSOR arm)34set(CMAKE_C_COMPILER arm-none-eabi-gcc)5set(CMAKE_CXX_COMPILER arm-none-eabi-g++)6set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)7set(CMAKE_OBJCOPY arm-none-eabi-objcopy)8set(CMAKE_SIZE arm-none-eabi-size)910# Prevent CMake from testing the compiler with a host link11set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)CMakeLists.txt for STM32F411:
1cmake_minimum_required(VERSION 3.16)2project(stm32_project C ASM)34# CPU and FPU flags5set(CPU_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")6set(CMAKE_C_FLAGS "${CPU_FLAGS} -Os -Wall -fdata-sections -ffunction-sections")7set(CMAKE_EXE_LINKER_FLAGS8 "${CPU_FLAGS} -specs=nano.specs -T${CMAKE_SOURCE_DIR}/STM32F411RETx_FLASH.ld \9 -Wl,--gc-sections -Wl,-Map=${PROJECT_NAME}.map")1011# Sources12file(GLOB_RECURSE SOURCES13 "Core/Src/*.c"14 "Drivers/STM32F4xx_HAL_Driver/Src/*.c"15 "Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f411xe.s"16)1718# Includes19include_directories(20 Core/Inc21 Drivers/STM32F4xx_HAL_Driver/Inc22 Drivers/CMSIS/Device/ST/STM32F4xx/Include23 Drivers/CMSIS/Include24)2526add_definitions(-DSTM32F411xE -DUSE_HAL_DRIVER)2728add_executable(${PROJECT_NAME}.elf ${SOURCES})2930# Post-build: create .bin and .hex31add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD32 COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${PROJECT_NAME}.elf>33 ${PROJECT_NAME}.bin34 COMMAND ${CMAKE_OBJCOPY} -O ihex $<TARGET_FILE:${PROJECT_NAME}.elf>35 ${PROJECT_NAME}.hex36 COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${PROJECT_NAME}.elf>37)Build:
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=../arm-none-eabi-toolchain.cmake
ninja
Example 5 — ESP32 ESP-IDF Component System
ESP-IDF wraps CMake in its own Component System. Instead of add_executable(), you use idf_component_register().
Minimal ESP32 project structure:
my_project/
├── CMakeLists.txt ← Top-level (2 lines)
└── main/
├── CMakeLists.txt ← Component (4 lines)
└── main.c
Top-level CMakeLists.txt:
1cmake_minimum_required(VERSION 3.16)2include($ENV{IDF_PATH}/tools/cmake/project.cmake)3project(my_project)main/CMakeLists.txt:
1idf_component_register(2 SRCS "main.c"3 INCLUDE_DIRS "."4)Adding a custom component:
my_project/
├── CMakeLists.txt
├── main/
│ ├── CMakeLists.txt
│ └── main.c
└── components/
└── my_sensor/
├── CMakeLists.txt ← Component registration
├── my_sensor.c
└── include/
└── my_sensor.h
components/my_sensor/CMakeLists.txt:
1idf_component_register(2 SRCS "my_sensor.c"3 INCLUDE_DIRS "include"4 REQUIRES driver esp_log5)Then in main/CMakeLists.txt, add REQUIRES my_sensor:
1idf_component_register(2 SRCS "main.c"3 INCLUDE_DIRS "."4 REQUIRES my_sensor5)ESP-IDF automatically builds my_sensor as a static library and links it to your app.
Useful CMake Variables
| Variable | Purpose |
|---|---|
CMAKE_C_COMPILER | C compiler to use |
CMAKE_BUILD_TYPE | Debug, Release, RelWithDebInfo |
CMAKE_SOURCE_DIR | Root directory of the project |
CMAKE_BINARY_DIR | Build directory (where you run cmake from) |
CMAKE_CURRENT_SOURCE_DIR | Directory of the current CMakeLists.txt |
PROJECT_NAME | Name from project() command |
CMAKE_TOOLCHAIN_FILE | Path to cross-compilation toolchain file |
CMake Build Types
# Debug — no optimization, full debug symbols
cmake .. -DCMAKE_BUILD_TYPE=Debug
# Release — maximum optimization, no debug symbols
cmake .. -DCMAKE_BUILD_TYPE=Release
# RelWithDebInfo — optimized + debug symbols (best for profiling)
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
CMake Quick Reference
| Command | Purpose |
|---|---|
cmake_minimum_required(VERSION x.x) | Minimum CMake version |
project(Name LANGUAGES C CXX) | Name the project |
add_executable(name src1.c src2.c) | Create an executable |
add_library(name STATIC src.c) | Create a static library |
target_include_directories(name PUBLIC dir/) | Add include paths |
target_link_libraries(name PRIVATE lib) | Link a library |
target_compile_options(name PRIVATE -Wall) | Add compiler flags |
add_definitions(-DFOO=1) | Add preprocessor defines |
add_subdirectory(subdir/) | Include a sub-project |
file(GLOB_RECURSE SRCS src/*.c) | Collect all .c files |
cmake .. -G Ninja | Generate Ninja files |
cmake --build . --parallel | Build all with max threads |

