Build Systems

Introduction to Build Systems

When you click the "Compile" button in Arduino IDE, something very specific happens behind the scenes. Understanding what that is — and why professional embedded projects use Make, CMake, and Ninja instead — is the foundation of this section.


What Happens When You Click "Compile" in Arduino?

Arduino IDE hides the build process from beginners. When you click the ✓ button, it actually runs something like this:

bash
# Arduino calls avr-gcc (for Uno) or xtensa-esp32-elf-gcc (for ESP32)
# with a huge automatically-generated command:

xtensa-esp32-elf-gcc \
  -DARDUINO=10819 \
  -DARDUINO_ESP32_DEV \
  -DESP_PLATFORM \
  -DF_CPU=240000000L \
  -DCORE_DEBUG_LEVEL=0 \
  -I/home/user/.arduino15/packages/esp32/hardware/esp32/2.0.14/cores/esp32 \
  -I/home/user/.arduino15/packages/esp32/hardware/esp32/2.0.14/variants/esp32 \
  -I/home/user/.arduino15/packages/esp32/tools/xtensa-esp32-elf-gcc/include \
  -mlongcalls -mtext-section-literals -falign-functions=4 \
  -Os -g3 -Wall -Wextra \
  -c /tmp/sketch123/sketch.ino.cpp \
  -o /tmp/sketch123/sketch.ino.cpp.o

That is one compiler command — and it only compiles your sketch. Arduino then runs dozens more to compile every library you included, then links them all together. This is exactly what a Build System automates.


The Two Core Problems Build Systems Solve

Problem 1 — Remembering Everything

A production ESP32 project may have:

  • Your source files (20+ .c and .h files)
  • FreeRTOS (50+ files)
  • WiFi stack (100+ files)
  • Driver libraries (30+ files)
  • Compiler flags specific to the ESP32 Xtensa core
  • Linker scripts defining memory layout

Nobody types all of this by hand. A build system reads a short config file you write and generates all the right commands automatically.

Problem 2 — Only Rebuilding What Changed

If your project has 200 source files and takes 3 minutes to compile from scratch, you cannot recompile all 200 files every time you fix a typo in one file.

Build systems track which source files changed since the last build and only recompile those files. This is called an incremental build. For ESP-IDF projects, this reduces rebuild time from 3 minutes to under 5 seconds.


The Build Tool Family Tree

text
Your Code


┌─────────────────────────────────────────────────────┐
│                   CMake                              │
│  (You write CMakeLists.txt — cross-platform config) │
└───────────────────────┬─────────────────────────────┘
                        │  Generates
              ┌─────────┴──────────┐
              ▼                    ▼
        ┌──────────┐         ┌──────────┐
        │ Makefile │         │build.ninja│
        │  (Make)  │         │  (Ninja) │
        └────┬─────┘         └────┬─────┘
             │ Runs                │ Runs
             ▼                    ▼
        ┌────────────────────────────────┐
        │       GCC / Clang / MSVC       │
        │    (The actual C compiler)     │
        └────────────────────────────────┘


              ┌──────────────────┐
              │  firmware.bin    │
              │  (flashable to   │
              │   ESP32/STM32)   │
              └──────────────────┘

How This Compares Across Platforms

PlatformBuild TriggerGeneratorBuild ToolCompiler
Arduino IDEClick ✓Arduino CLI (internal)Arduino CLIavr-gcc / xtensa-esp32-elf-gcc
ESP32 ESP-IDFidf.py buildCMakeNinjaxtensa-esp32-elf-gcc
STM32CubeIDEClick ▶Eclipse CDT (internal)Makearm-none-eabi-gcc
STM32 (CLI)cmake && ninjaCMakeNinjaarm-none-eabi-gcc
Raspberry Pimake or cmakeCMakeMake or Ninjagcc (native ARM)
Desktop Cgcc main.c / makeManual or CMakeMake or Ninjagcc / clang

What idf.py build Actually Does

When you run idf.py build for an ESP32 project, Python does this sequence automatically:

bash
# Step 1: CMake configure — read your CMakeLists.txt, find all components,
#         and generate build/build.ninja
cmake -G Ninja \
      -DIDF_TARGET=esp32 \
      -DCMAKE_TOOLCHAIN_FILE=$IDF_PATH/tools/cmake/toolchain-esp32.cmake \
      ..

# Step 2: Ninja build — read build.ninja, compile only changed files,
#         link everything into firmware.elf
ninja -C build

# Step 3: Binary conversion — convert .elf to flashable .bin
esptool.py --chip esp32 elf2image build/project.elf

idf.py is a Python wrapper that runs these three steps for you. Once you understand this, you can run them manually when debugging build issues.


What STM32CubeIDE Does Under the Hood

STM32CubeIDE is an Eclipse-based IDE. When you click ▶ (Build), it runs arm-none-eabi-gcc with a Makefile it generates from your project settings. The Makefile looks like this:

makefile
# STM32 auto-generated Makefile (simplified)
TARGET = my_stm32_project
CPU    = -mcpu=cortex-m4
FPU    = -mfpu=fpv4-sp-d16 -mfloat-abi=hard
CC     = arm-none-eabi-gcc

SOURCES = Core/Src/main.c \
          Core/Src/stm32f4xx_it.c \
          Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c

INCLUDES = -ICore/Inc \
           -IDrivers/CMSIS/Include \
           -IDrivers/STM32F4xx_HAL_Driver/Inc

$(TARGET).elf: $(SOURCES)
	$(CC) $(CPU) $(FPU) $(INCLUDES) -O2 -Wall $(SOURCES) \
	    -T STM32F411RETx_FLASH.ld \
	    -o $(TARGET).elf

You can export this Makefile and run make from the terminal — no IDE required. This is how CI/CD pipelines build STM32 firmware headlessly.


The Build Output Files

FileWhat It Is
.oObject file — one compiled source file, not yet linked
.aStatic library — archive of multiple .o files
.elfExecutable and Linkable Format — the complete program with debug symbols
.binRaw binary — the .elf stripped to just bytes for flashing
.hexIntel HEX format — ASCII representation of the binary, used by some programmers
.mapLinker map — lists every function and its RAM/Flash address
.lstAssembly listing — C code side-by-side with generated assembly

Summary: Build System at a Glance

ToolRoleYou Write
MakeExecutes build recipesMakefile
CMakeGenerates Make/Ninja filesCMakeLists.txt
NinjaFaster replacement for MakeNever — CMake generates it
idf.pyCMake + Ninja wrapper for ESP-IDFCMakeLists.txt per component
GCC / ClangThe actual C compiler.c and .h source files

In the next sections we go deep into each layer: Makefiles, CMake, and Ninja — with examples for both desktop C and microcontroller firmware.

Previous
Interview Questions (Python/ML/DL)