feat(cmakev2): Added multi_binary example for cmakev2

This commit adds a new example at
examples/build_system/cmakev2/features/multi_binary to
demonstrate the cmakev2 ability to build multiple binaries in a single
CMake project.
This commit is contained in:
Sudeep Mohanty
2026-01-27 11:17:25 +01:00
committed by Frantisek Hrbata
parent 1a51a0ad01
commit 66851c702e
16 changed files with 299 additions and 0 deletions
+15
View File
@@ -167,6 +167,21 @@ Demonstrates the use of the Kconfig configuration system to conditionally includ
**When to use:** When you need to build applications or components that must conditionally depend on other components based on `sdkconfig` options.
---
### multi_binary
**Location:** [multi_binary](./features/multi_binary/)
Demonstrates building multiple independent firmware binaries from a single project in one build command.
**Build:** `idf.py build` creates both `app1.bin` and `app2.bin`
**Flash App1:** `idf.py app1-flash monitor`
**Flash App2:** `idf.py app2-flash monitor`
**Key Concepts:** `idf_build_executable()`, `idf_build_binary()`, `idf_flash_binary()`, conditional component linking, Kconfig integration, multiple entry points
**When to use:** Creating firmware variants with different features, manufacturing test firmware, or developing modular applications with compile-time configurable behavior.
## Build System v2 API Quick Reference
### Project Setup
@@ -0,0 +1,54 @@
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(multi_binary C CXX ASM)
# Manual initialization for fine-grained control over component inclusion
idf_project_init()
# Build the first executable: app1
# Links: app1_main, component1, component2
idf_build_executable(app1.elf
COMPONENTS app1_main component1 component2)
# Build the second executable: app2
# Links: app2_main, component1, component2, component3
idf_build_executable(app2.elf
COMPONENTS app2_main component1 component2 component3)
# Generate binaries and flash targets
# App1 binary
idf_build_binary(app1.elf
OUTPUT_FILE "${CMAKE_BINARY_DIR}/app1.bin"
TARGET app1_binary)
idf_flash_binary(app1_binary
TARGET app1-flash
NAME "app1"
FLASH)
# Generate metadata only for the primary app1 binary
idf_build_generate_metadata(BINARY app1_binary)
# Create menuconfig and confserver targets for app1 binary
idf_create_menuconfig(app1.elf TARGET app1-menuconfig)
idf_create_confserver(app1.elf TARGET app1-confserver)
# App2 binary
idf_build_binary(app2.elf
OUTPUT_FILE "${CMAKE_BINARY_DIR}/app2.bin"
TARGET app2_binary)
idf_flash_binary(app2_binary
TARGET app2-flash
NAME "app2"
FLASH)
# Create menuconfig and confserver targets for app2 binary
idf_create_menuconfig(app2.elf TARGET app2-menuconfig)
idf_create_confserver(app2.elf TARGET app2-confserver)
# Make both binaries part of the default "app" target
add_custom_target(app ALL DEPENDS app1.bin app2.bin)
@@ -0,0 +1,92 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
# Multi-Binary Example: Multiple Apps with Different Component Sets
This example demonstrates **Build System v2's** capability to build multiple independent firmware binaries from a single project in one build command. Each binary has its own entry point component with different dependencies.
## Overview
Two independent executables are built simultaneously:
1. **`app1.bin`** - Links `app1_main`, `component1`, `component2`
2. **`app2.bin`** - Links `app2_main`, `component1`, `component2`, `component3`
Each app has its own dedicated entry point component that explicitly declares which components it needs. This avoids complex conditional logic and makes dependencies clear.
## Project Structure
All components live under `components/`. Each `idf_build_executable` call picks exactly the subset it requires.
```
multi_binary/
├── CMakeLists.txt
└── components/
├── app1_main/ # Entry point for app1
│ ├── CMakeLists.txt
│ └── app1_main.c
├── app2_main/ # Entry point for app2
│ ├── CMakeLists.txt
│ └── app2_main.c
├── component1/ # Shared component
│ ├── CMakeLists.txt
│ ├── component1.c
│ └── component1.h
├── component2/ # Shared component
│ ├── CMakeLists.txt
│ ├── component2.c
│ └── component2.h
└── component3/ # Only linked into app2
├── CMakeLists.txt
├── component3.c
└── component3.h
```
## Building
### Build both binaries in a single command:
```bash
cd examples/build_system/cmakev2/features/multi_binary
idf.py set-target <target>
idf.py build
```
This generates both `app1.bin` and `app2.bin` in the `build/` directory.
## Flashing
Flash app1:
```bash
idf.py app1-flash monitor
```
Flash app2:
```bash
idf.py app2-flash monitor
```
You can switch between apps without rebuilding - just select which one to flash.
### Configuration (menuconfig)
When invoking a custom menuconfig target via `idf.py`, you must pass `--no-hints`. For example, use `idf.py app1-menuconfig --no-hints` (plain `idf.py app1-menuconfig` may not work because `idf.py` redirects stdout by default, which breaks the curses-based menu).
> **Note:** Although multiple binaries can be produced from a single project, each component is evaluated only once using the current configuration. If a different configuration is needed for a component, a new project must be created. You cannot use the same component with different configurations within a single project.
## Expected Output
### app1 Output
```
I (xxx) component1: Hello from component1!
I (xxx) component2: Hello from component2!
```
### app2 Output
```
I (xxx) component1: Hello from component1!
I (xxx) component2: Hello from component2!
I (xxx) component3: Hello from component3!
```
@@ -0,0 +1,2 @@
idf_component_register(SRCS "app1_main.c"
PRIV_REQUIRES component1 component2)
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "component1.h"
#include "component2.h"
void app_main(void)
{
component1_print_hello();
component2_print_hello();
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
@@ -0,0 +1,2 @@
idf_component_register(SRCS "app2_main.c"
PRIV_REQUIRES component1 component2 component3)
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "component1.h"
#include "component2.h"
#include "component3.h"
void app_main(void)
{
component1_print_hello();
component2_print_hello();
component3_print_hello();
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
@@ -0,0 +1,2 @@
idf_component_register(SRCS "component1.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_log.h"
static const char *TAG = "component1";
void component1_print_hello(void)
{
ESP_LOGI(TAG, "Hello from component1!");
}
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COMPONENT1_H
#define COMPONENT1_H
void component1_print_hello(void);
#endif // COMPONENT1_H
@@ -0,0 +1,2 @@
idf_component_register(SRCS "component2.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_log.h"
static const char *TAG = "component2";
void component2_print_hello(void)
{
ESP_LOGI(TAG, "Hello from component2!");
}
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COMPONENT2_H
#define COMPONENT2_H
void component2_print_hello(void);
#endif // COMPONENT2_H
@@ -0,0 +1,2 @@
idf_component_register(SRCS "component3.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_log.h"
#include "sdkconfig.h"
static const char *TAG = "component3";
void component3_print_hello(void)
{
ESP_LOGI(TAG, "Hello from component3!");
}
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COMPONENT3_H
#define COMPONENT3_H
void component3_print_hello(void);
#endif // COMPONENT3_H