Skip to content

Commit 0f42ea7

Browse files
mfialafDeveloper-Portal-BOT
authored andcommitted
blog: Added article about CMake Pretests
1 parent 3ddb407 commit 0f42ea7

File tree

2 files changed

+333
-0
lines changed

2 files changed

+333
-0
lines changed
1.07 MB
Loading
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
---
2+
title: "Manage multiple ESP-IDF build configurations with ease"
3+
date: "2025-12-05"
4+
showAuthor: false
5+
authors:
6+
- "marek-fiala"
7+
tags: ["ESP-IDF", "CMake", "Build System", "Configuration"]
8+
summary: "Tired of juggling complex command-line arguments with endless `idf.py` flags? ESP-IDF v6.0 introduces a new feature, letting you switch between multiple build configuration. Learn how to structure your project, isolate sdkconfig files, and migrate from ad-hoc commands to clean, declarative builds."
9+
---
10+
11+
As ESP-IDF projects grow more sophisticated, developers often need multiple build configurations: development builds with debug symbols, optimized production releases, size-constrained builds for specific hardware, and comprehensive testing setups. While ESP-IDF has always supported these scenarios, they typically required remembering complex command-line arguments and managing multiple build directories manually.
12+
13+
ESP-IDF v6.0 introduces a powerful solution: reusable configuration profiles, called presets, that handle all this complexity for you. These configuration presets make switching between development, production, and testing environments effortless. This is how the command changes:
14+
- Old: `idf.py -B build_prod -D SDKCONFIG_DEFAULTS="prod.cfg" build`
15+
- New: `idf.py --preset production build`
16+
17+
## Traditional build management
18+
19+
Let's first examine the current approach to managing multiple build configurations. Consider a project that needs:
20+
21+
- **Development builds**: Fast iteration with debug symbols
22+
- **Production builds**: Optimized binaries for various hardware variants
23+
- **Testing builds**: Special configurations for automated testing
24+
25+
Traditionally, you might handle this with complex command lines like:
26+
27+
```bash
28+
# Development build
29+
idf.py build
30+
31+
# Production variant 1
32+
idf.py -B build_prod1 -D SDKCONFIG_DEFAULTS="sdkconfig.prod_common;sdkconfig.prod1" build
33+
34+
# Production variant 2
35+
idf.py -B build_prod2 -D SDKCONFIG_DEFAULTS="sdkconfig.prod_common;sdkconfig.prod2" build
36+
37+
# Testing with custom settings
38+
idf.py -B build_test -D SDKCONFIG_DEFAULTS="sdkconfig.test" -D SDKCONFIG="build_test/sdkconfig" build
39+
```
40+
41+
## How configuration presets work
42+
43+
ESP-IDF v6.0 introduces support for build configuration presets, allowing you to define reusable build settings in JSON files. This feature is built on CMake Presets, a standard CMake feature that ESP-IDF now supports. Each preset can specify:
44+
45+
- **Build directory location** (`binaryDir`)
46+
- **CMake cache variables** (including `SDKCONFIG`, `SDKCONFIG_DEFAULTS`, and optionally `IDF_TARGET`)
47+
- **Generator preferences** (Ninja, Make, etc.)
48+
- **Human-readable metadata** (display names and descriptions)
49+
50+
The key benefit is **declarative configuration**: instead of remembering complex command-line arguments, you define your configurations once in a file and reference them by name.
51+
52+
ESP-IDF will automatically detect and use configuration presets when either `CMakePresets.json` or `CMakeUserPresets.json` is present in your project root directory. Example of how preset configuration file looks like can be found later in section [Getting started with configuration presets.](#getting-started-with-configuration-presets)
53+
54+
### Working with build directories
55+
56+
One of the biggest advantages of this feature is clean build directory management. Each preset can specify its own build directory, allowing you to maintain multiple configurations simultaneously:
57+
58+
```bash
59+
project/
60+
├── CMakePresets.json
61+
├── build/
62+
│ ├── default/ # Development builds
63+
│ ├── release/ # Release builds
64+
│ ├── size-opt/ # Size-optimized builds
65+
│ └── testing/ # Test builds
66+
├── sdkconfig.defaults.common
67+
├── sdkconfig.defaults.release
68+
└── sdkconfig.defaults.size-opt
69+
```
70+
71+
This structure provides several benefits:
72+
73+
- **Parallel builds**: Switch between configurations without rebuilding
74+
- **Clean separation**: No cross-contamination between build types
75+
- **Easy cleanup**: Remove specific build types without affecting others
76+
- **Flexible configuration**: Presets can specify different optimization levels, debug settings, and target chips for various build scenarios
77+
78+
### Custom sdkconfig locations
79+
80+
By default, ESP-IDF places the `sdkconfig` file in your project root. With presets, you can keep configuration files organized by placing them in build directories:
81+
82+
```json
83+
{
84+
"name": "isolated-config",
85+
"binaryDir": "build/isolated",
86+
"cacheVariables": {
87+
"SDKCONFIG": "./build/isolated/sdkconfig"
88+
}
89+
}
90+
```
91+
92+
This approach provides the following advantages:
93+
- Keeps your project root clean
94+
- Prevents accidental commits of development configurations
95+
- Makes it clear which sdkconfig belongs to which build
96+
- Enables easier automated testing of different configurations
97+
98+
### Setting default selection with environment variable
99+
100+
For team workflows, you can set default preset using the environment variable `IDF_PRESET`, for your shell session.
101+
102+
Unix:
103+
```bash
104+
export IDF_PRESET=release # bash
105+
```
106+
107+
Windows:
108+
```PowerShell
109+
$ENV:IDF_PRESET=release # PowerShell
110+
```
111+
112+
```bash
113+
# Now these commands use the 'release' preset automatically
114+
idf.py build
115+
idf.py flash monitor
116+
```
117+
118+
This is particularly useful in CI/CD pipelines:
119+
120+
```yaml
121+
# GitHub Actions example
122+
- name: Build production firmware
123+
env:
124+
IDF_PRESET: release
125+
run: |
126+
idf.py build
127+
idf.py size
128+
```
129+
130+
### Selection logic
131+
132+
ESP-IDF follows a clear precedence order for preset selection:
133+
134+
1. **Command-line argument**: `idf.py --preset my-preset build`
135+
2. **Environment variable**: set environmental variable `IDF_PRESET`.
136+
3. **Automatic selection**:
137+
- If a preset named `default` exists, use it
138+
- Otherwise, use the first preset in the preset configuration file
139+
140+
This makes preset usage flexible while providing sensible defaults.
141+
142+
## Getting started with configuration presets
143+
144+
### Step 1: Create your JSON file
145+
146+
Create a `CMakePresets.json` file in your project root directory. Here's a comprehensive example that demonstrates multiple production variants:
147+
148+
```json
149+
{
150+
"version": 3,
151+
"configurePresets": [
152+
{
153+
"name": "default",
154+
"binaryDir": "build/default",
155+
"displayName": "Development Configuration",
156+
"description": "Fast builds for development and debugging",
157+
"cacheVariables": {
158+
"IDF_TARGET": "esp32s3",
159+
"SDKCONFIG": "./build/default/sdkconfig"
160+
}
161+
},
162+
{
163+
"name": "release",
164+
"binaryDir": "build/release",
165+
"displayName": "Release Build",
166+
"cacheVariables": {
167+
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.common;sdkconfig.defaults.release",
168+
"SDKCONFIG": "./build/release/sdkconfig"
169+
}
170+
},
171+
{
172+
"name": "size-opt",
173+
"binaryDir": "build/size-opt",
174+
"displayName": "Size Optimized Build",
175+
"cacheVariables": {
176+
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.common;sdkconfig.defaults.size-opt",
177+
"SDKCONFIG": "./build/size-opt/sdkconfig"
178+
}
179+
},
180+
{
181+
"name": "testing",
182+
"binaryDir": "build/testing",
183+
"displayName": "Testing Configuration",
184+
"cacheVariables": {
185+
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.testing",
186+
"SDKCONFIG": "./build/testing/sdkconfig"
187+
}
188+
}
189+
]
190+
}
191+
```
192+
193+
> **Note**: The `version` field is set to `3` to match the CMake Presets schema supported by ESP-IDF's minimum required CMake version (3.22.1). If you're using a newer CMake version, you can use a higher version number for additional features.
194+
195+
### Step 2: Create your configuration files
196+
197+
Alongside your JSON file, create the corresponding configuration files:
198+
199+
**sdkconfig.defaults.common**:
200+
```bash
201+
# Common settings for all build variants
202+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
203+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
204+
CONFIG_FREERTOS_HZ=100
205+
```
206+
207+
**sdkconfig.defaults.release**:
208+
```bash
209+
# Release build optimizations
210+
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
211+
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
212+
```
213+
214+
**sdkconfig.defaults.size-opt**:
215+
```bash
216+
# Aggressive size optimizations
217+
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
218+
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
219+
```
220+
221+
Notice how different presets use different combinations of configuration files through `SDKCONFIG_DEFAULTS`. The common settings are shared via `sdkconfig.defaults.common`, while build-specific optimizations are applied through additional files like `sdkconfig.defaults.release` or `sdkconfig.defaults.size-opt`. Additionally, the `default` preset shows how to set the `IDF_TARGET` variable to specify the target chip, which is optional but helps maintain a consistent development target.
222+
223+
**sdkconfig.defaults.testing**:
224+
```bash
225+
# Testing and debugging settings
226+
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
227+
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
228+
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
229+
```
230+
231+
### Step 3: Use your presets
232+
233+
Now you can build, flash, and monitor with clean, simple commands:
234+
235+
```bash
236+
# Development build (automatically selects 'default' preset)
237+
idf.py build
238+
239+
# Production builds with different optimizations
240+
idf.py --preset release build
241+
idf.py --preset size-opt build
242+
243+
# Flash and monitor with specific preset
244+
idf.py --preset release -p /dev/ttyUSB0 flash monitor
245+
246+
# Testing build
247+
idf.py --preset testing build
248+
```
249+
250+
## Current limitations
251+
252+
There is one current limitation to be aware of: the `inherits` field for preset inheritance isn't currently supported. If you try to use it, ESP-IDF will show a warning and the inheritance will be ignored.
253+
254+
## Migrating from manual build configurations
255+
256+
If you're currently using complex build scripts or manual command-line arguments, here's how to migrate:
257+
258+
### Step 1: Audit your current configurations
259+
260+
List all the different ways you currently build your project:
261+
262+
```bash
263+
# Current manual commands
264+
idf.py build # Development
265+
idf.py -B build_prod -D SDKCONFIG_DEFAULTS="sdkconfig.defaults.production" # Production
266+
idf.py -B build_test -D SPECIAL_FLAG=1 # Testing
267+
```
268+
269+
### Step 2: Extract common patterns
270+
271+
Identify shared settings and create defaults files:
272+
273+
- **sdkconfig.defaults** (development - already exists)
274+
- **sdkconfig.defaults.production**
275+
- **sdkconfig.defaults.testing**
276+
277+
### Step 3: Create equivalent presets
278+
279+
Convert each command pattern to an equivalent entry:
280+
281+
```json
282+
{
283+
"version": 3,
284+
"configurePresets": [
285+
{
286+
"name": "default",
287+
"binaryDir": "build",
288+
"displayName": "Development"
289+
},
290+
{
291+
"name": "production",
292+
"binaryDir": "build_prod",
293+
"cacheVariables": {
294+
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.production"
295+
}
296+
},
297+
{
298+
"name": "testing",
299+
"binaryDir": "build_test",
300+
"cacheVariables": {
301+
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.testing",
302+
"SPECIAL_FLAG": "1"
303+
}
304+
}
305+
]
306+
}
307+
```
308+
309+
### Step 4: Update documentation and scripts
310+
311+
Replace build instructions:
312+
- Old: "Run `idf.py -B build_prod -D SDKCONFIG_DEFAULTS="sdkconfig.defaults.production" build`"
313+
- New: "Run `idf.py --preset production build`"
314+
315+
## Conclusion
316+
317+
Configuration presets transform ESP-IDF build management from a manual, error-prone process into a declarative, maintainable system. By defining your build configurations once in `CMakePresets.json`, you can:
318+
319+
- **Eliminate command-line complexity** with simple preset names
320+
- **Maintain multiple configurations** without cross-contamination
321+
- **Standardize team workflows** with shared preset definitions
322+
- **Integrate seamlessly** with IDEs and CI/CD pipelines
323+
- **Scale** as your project grows more complex
324+
325+
Whether you're managing a simple project with development and production builds, or a complex system with different optimization levels and testing configurations, this approach provides the structure and simplicity you need to focus on building great products instead of wrestling with build systems.
326+
327+
## What's next?
328+
329+
- Explore the [ESP-IDF multi-config example](https://github.com/espressif/esp-idf/tree/release/v6.0/examples/build_system/cmake/multi_config) for hands-on practice
330+
- Check out the [CMake Presets documentation](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) for advanced features
331+
- Consider contributing preset examples for common ESP-IDF use cases
332+
333+
Start small with a simple development/production split, then expand your build configurations as your workflow matures. Your future self (and your teammates) will thank you for the clarity and consistency that configuration presets brings to your ESP-IDF projects.

0 commit comments

Comments
 (0)