Skip to content

Commit c7b4804

Browse files
Merge branch 'feat/cmakev2_features_and_test_updates' into 'master'
Enable more cmakev2 CI tests and support dfu, uf2 targets Closes IDF-14181, IDF-14182, and IDF-14183 See merge request espressif/esp-idf!43460
2 parents 47d6c9c + cad8054 commit c7b4804

File tree

10 files changed

+266
-23
lines changed

10 files changed

+266
-23
lines changed

.gitlab/ci/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ pytest_buildv2_system:
274274
test_common.py
275275
test_components.py
276276
test_cmake.py
277+
test_idf_extension.py
278+
test_rebuild.py
277279

278280
pytest_build_system_macos:
279281
extends:

.gitlab/ci/test-win.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,5 @@ pytest_buildv2_system_win:
178178
test_common.py
179179
test_components.py
180180
test_cmake.py
181+
test_idf_extension.py
182+
test_rebuild.py

tools/cmakev2/dfu.cmake

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
#[[
5+
.. cmakev2:function:: idf_create_dfu
6+
7+
.. code-block:: cmake
8+
9+
idf_create_dfu(<binary>
10+
TARGET <target>)
11+
12+
*binary[in]*
13+
14+
Binary target for which to create DFU targets. The binary target is
15+
created by :cmakev2:ref:`idf_build_binary`.
16+
17+
*TARGET[in]*
18+
19+
Name of the DFU generation target to be created (e.g., "dfu", "dfu-app").
20+
Also creates "<target>-list" and "<target>-flash" targets.
21+
22+
Create DFU (Device Firmware Update) targets for the specified binary target.
23+
DFU is only supported on certain targets. For other
24+
targets, this function does nothing.
25+
26+
Note: DFU is not supported when secure boot is enabled. This function will
27+
not create DFU targets if secure boot is enabled.
28+
29+
Three targets are created:
30+
- <TARGET>: Generates the DFU binary file from flasher_args.json
31+
- <TARGET>-list: Lists connected DFU devices
32+
- <TARGET>-flash: Flashes the DFU binary to a connected device
33+
34+
Example usage for default project:
35+
idf_build_binary(myapp TARGET myapp_binary OUTPUT_FILE myapp.bin)
36+
idf_create_dfu(myapp_binary TARGET dfu)
37+
38+
#]]
39+
function(idf_create_dfu binary)
40+
set(options)
41+
set(one_value TARGET)
42+
set(multi_value)
43+
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
44+
45+
if(NOT DEFINED ARG_TARGET)
46+
idf_die("TARGET option is required")
47+
endif()
48+
49+
if(NOT TARGET "${binary}")
50+
idf_die("Binary target '${binary}' is not a cmake target")
51+
endif()
52+
53+
# DFU is not supported when secure boot is enabled
54+
if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES)
55+
idf_msg("DFU not supported when secure boot is enabled, skipping DFU target creation")
56+
return()
57+
endif()
58+
59+
# Determine DFU PID based on target
60+
idf_build_get_property(target IDF_TARGET)
61+
62+
if("${target}" STREQUAL "esp32s2")
63+
set(dfu_pid "2")
64+
elseif("${target}" STREQUAL "esp32s3")
65+
set(dfu_pid "9")
66+
elseif("${target}" STREQUAL "esp32p4")
67+
set(dfu_pid "12")
68+
else()
69+
# DFU not supported on this target
70+
idf_msg("DFU not supported on ${target}, skipping DFU target creation")
71+
return()
72+
endif()
73+
74+
# Get build properties
75+
idf_build_get_property(python PYTHON)
76+
idf_build_get_property(idf_path IDF_PATH)
77+
idf_build_get_property(build_dir BUILD_DIR)
78+
79+
# Path to DFU output file
80+
set(dfu_output_file "${build_dir}/${ARG_TARGET}.bin")
81+
82+
# Create DFU generation target
83+
add_custom_target(${ARG_TARGET}
84+
COMMAND ${python} ${idf_path}/tools/mkdfu.py write
85+
-o "${dfu_output_file}"
86+
--json "${build_dir}/flasher_args.json"
87+
--pid "${dfu_pid}"
88+
--flash-size "${CONFIG_ESPTOOLPY_FLASHSIZE}"
89+
DEPENDS ${binary}
90+
VERBATIM
91+
USES_TERMINAL
92+
COMMENT "Generating DFU binary for ${binary}"
93+
)
94+
95+
# Add dependency on bootloader if it's being built
96+
if(CONFIG_APP_BUILD_BOOTLOADER AND TARGET bootloader)
97+
add_dependencies(${ARG_TARGET} bootloader)
98+
endif()
99+
100+
# Create DFU list target (lists connected DFU devices)
101+
add_custom_target(${ARG_TARGET}-list
102+
COMMAND ${CMAKE_COMMAND}
103+
-D ESP_DFU_LIST="1"
104+
-P ${idf_path}/tools/cmake/run_dfu_util.cmake
105+
USES_TERMINAL
106+
COMMENT "Listing DFU devices"
107+
)
108+
109+
# Create DFU flash target (flashes the DFU binary)
110+
add_custom_target(${ARG_TARGET}-flash
111+
COMMAND ${CMAKE_COMMAND}
112+
-D ESP_DFU_BIN="${dfu_output_file}"
113+
-D ESP_DFU_PID="${dfu_pid}"
114+
-P ${idf_path}/tools/cmake/run_dfu_util.cmake
115+
DEPENDS ${ARG_TARGET}
116+
USES_TERMINAL
117+
COMMENT "Flashing DFU binary for ${binary}"
118+
)
119+
120+
idf_msg("Created DFU targets: ${ARG_TARGET}, ${ARG_TARGET}-list, ${ARG_TARGET}-flash")
121+
endfunction()

tools/cmakev2/idf.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ include(project)
3434
include(manager)
3535
include(compat)
3636
include(ldgen)
37+
include(dfu)
38+
include(uf2)
3739
include(GetGitRevisionDescription)
3840
# For backward compatibility, since externalproject_add is used by
3941
# project_include.cmake in the bootloader component. The ExternalProject

tools/cmakev2/project.cmake

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,9 @@ macro(idf_project_default)
747747
TARGET app-flash
748748
NAME "app"
749749
FLASH)
750+
751+
idf_create_dfu("${executable}_binary"
752+
TARGET dfu)
750753
endif()
751754

752755
# FIXME: Dependencies should be specified within the components, not in the
@@ -770,6 +773,12 @@ macro(idf_project_default)
770773

771774
idf_create_save_defconfig()
772775

776+
idf_create_uf2("${executable}"
777+
TARGET uf2)
778+
idf_create_uf2("${executable}"
779+
TARGET uf2-app
780+
APP_ONLY)
781+
773782
idf_build_generate_metadata("${executable}")
774783

775784
unset(build_dir)

tools/cmakev2/uf2.cmake

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
#[[
5+
.. cmakev2:function:: idf_create_uf2
6+
7+
.. code-block:: cmake
8+
9+
idf_create_uf2(<executable>
10+
TARGET <target>
11+
[APP_ONLY])
12+
13+
*executable[in]*
14+
15+
Executable target for which to create UF2 target.
16+
17+
*TARGET[in]*
18+
19+
Name of the UF2 generation target to be created (e.g., "uf2", "uf2-app").
20+
21+
*APP_ONLY[option]*
22+
23+
If specified, creates a UF2 binary file with only the application binary
24+
(uses --bin app option). If not specified, creates a UF2 binary file with
25+
all binaries from flasher_args.json.
26+
27+
Create a UF2 (USB Flashing Format) target for the specified executable.
28+
29+
Example usage:
30+
idf_create_uf2(myapp TARGET uf2) # All binaries
31+
idf_create_uf2(myapp TARGET uf2-app APP_ONLY) # App binary only
32+
#]]
33+
function(idf_create_uf2 executable)
34+
set(options APP_ONLY)
35+
set(one_value TARGET)
36+
set(multi_value)
37+
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
38+
39+
if(NOT DEFINED ARG_TARGET)
40+
idf_die("TARGET option is required")
41+
endif()
42+
43+
if(NOT TARGET "${executable}")
44+
idf_die("Executable '${executable}' is not a cmake target")
45+
endif()
46+
47+
# Get build properties
48+
idf_build_get_property(python PYTHON)
49+
idf_build_get_property(idf_path IDF_PATH)
50+
idf_build_get_property(build_dir BUILD_DIR)
51+
idf_build_get_property(target IDF_TARGET)
52+
53+
# Path to UF2 output file
54+
set(uf2_output_file "${build_dir}/${ARG_TARGET}.bin")
55+
56+
# Build UF2 command and arguments (matching cmakev1 pattern)
57+
set(UF2_ARGS --json "${build_dir}/flasher_args.json")
58+
set(UF2_CMD ${python} "${idf_path}/tools/mkuf2.py" write --chip ${target})
59+
60+
# Add output file and --bin app if APP_ONLY is specified
61+
set(uf2_args_list "${UF2_ARGS};-o;${uf2_output_file}")
62+
if(ARG_APP_ONLY)
63+
list(APPEND uf2_args_list --bin app)
64+
set(comment_msg "Generating UF2 app binary for ${executable}")
65+
else()
66+
set(comment_msg "Generating UF2 binary for ${executable}")
67+
endif()
68+
69+
# Create UF2 target (matching cmakev1 pattern using run_uf2_cmds.cmake)
70+
add_custom_target(${ARG_TARGET}
71+
COMMAND ${CMAKE_COMMAND}
72+
-D "IDF_PATH=${idf_path}"
73+
-D "UF2_CMD=${UF2_CMD}"
74+
-D "UF2_ARGS=${uf2_args_list}"
75+
-P "${idf_path}/tools/cmake/run_uf2_cmds.cmake"
76+
USES_TERMINAL
77+
VERBATIM
78+
COMMENT "${comment_msg}"
79+
)
80+
81+
idf_msg("Created UF2 target: ${ARG_TARGET}")
82+
endfunction()

tools/test_build_system/test_build.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ def test_build_fail_on_build_time(idf_py: IdfPyFunc, test_app_copy: Path) -> Non
172172
idf_py('build')
173173

174174

175-
@pytest.mark.buildv2_skip('dfu target not yet added in buildv2')
176175
@pytest.mark.usefixtures('test_app_copy')
177176
def test_build_dfu(idf_py: IdfPyFunc) -> None:
178177
logging.info('DFU build works')
@@ -188,7 +187,6 @@ def test_build_dfu(idf_py: IdfPyFunc) -> None:
188187
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/dfu.bin'])
189188

190189

191-
@pytest.mark.buildv2_skip('uf2 target not yet added in buildv2')
192190
@pytest.mark.usefixtures('test_app_copy')
193191
def test_build_uf2(idf_py: IdfPyFunc) -> None:
194192
logging.info('UF2 build works')
Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33
from .build_constants import ALL_ARTIFACTS
4+
from .build_constants import ALL_ARTIFACTS_BUILDV1
5+
from .build_constants import ALL_ARTIFACTS_BUILDV2
46
from .build_constants import APP_BINS
57
from .build_constants import BOOTLOADER_BINS
68
from .build_constants import JSON_METADATA
@@ -10,21 +12,38 @@
1012
from .file_utils import bin_files_differ
1113
from .file_utils import file_contains
1214
from .file_utils import replace_in_file
13-
from .idf_utils import EnvDict
1415
from .idf_utils import EXT_IDF_PATH
16+
from .idf_utils import EnvDict
17+
from .idf_utils import IdfPyFunc
1518
from .idf_utils import find_python
1619
from .idf_utils import get_idf_build_env
17-
from .idf_utils import IdfPyFunc
1820
from .idf_utils import run_cmake
1921
from .idf_utils import run_cmake_and_build
2022
from .idf_utils import run_idf_py
21-
from .snapshot import get_snapshot
2223
from .snapshot import Snapshot
24+
from .snapshot import get_snapshot
2325

2426
__all__ = [
25-
'append_to_file', 'replace_in_file',
26-
'get_idf_build_env', 'run_idf_py', 'EXT_IDF_PATH', 'EnvDict', 'IdfPyFunc',
27-
'Snapshot', 'get_snapshot', 'run_cmake', 'APP_BINS', 'BOOTLOADER_BINS',
28-
'PARTITION_BIN', 'JSON_METADATA', 'ALL_ARTIFACTS',
29-
'run_cmake_and_build', 'find_python', 'file_contains', 'bin_file_contains', 'bin_files_differ'
27+
'append_to_file',
28+
'replace_in_file',
29+
'get_idf_build_env',
30+
'run_idf_py',
31+
'EXT_IDF_PATH',
32+
'EnvDict',
33+
'IdfPyFunc',
34+
'Snapshot',
35+
'get_snapshot',
36+
'run_cmake',
37+
'APP_BINS',
38+
'BOOTLOADER_BINS',
39+
'PARTITION_BIN',
40+
'JSON_METADATA',
41+
'ALL_ARTIFACTS',
42+
'ALL_ARTIFACTS_BUILDV1',
43+
'ALL_ARTIFACTS_BUILDV2',
44+
'run_cmake_and_build',
45+
'find_python',
46+
'file_contains',
47+
'bin_file_contains',
48+
'bin_files_differ',
3049
]
Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33
BOOTLOADER_BINS = ['build/bootloader/bootloader.elf', 'build/bootloader/bootloader.bin']
44
APP_BINS = ['build/build_test_app.elf', 'build/build_test_app.bin']
55
PARTITION_BIN = ['build/partition_table/partition-table.bin']
6-
JSON_METADATA = ['build/project_description.json', 'build/flasher_args.json', 'build/config/kconfig_menus.json', 'build/config/sdkconfig.json']
7-
ALL_ARTIFACTS = [
8-
*BOOTLOADER_BINS,
9-
*APP_BINS,
10-
*PARTITION_BIN,
11-
*JSON_METADATA
12-
]
6+
JSON_METADATA = ['build/project_description.json', 'build/flasher_args.json', 'build/config/sdkconfig.json']
7+
8+
# kconfig_menus.json is only generated during build in cmakev1.
9+
KCONFIG_MENUS_JSON = ['build/config/kconfig_menus.json']
10+
11+
# Build system v1 artifacts
12+
ALL_ARTIFACTS_BUILDV1 = [*BOOTLOADER_BINS, *APP_BINS, *PARTITION_BIN, *JSON_METADATA, *KCONFIG_MENUS_JSON]
13+
14+
# Build system v2 artifacts
15+
ALL_ARTIFACTS_BUILDV2 = [*BOOTLOADER_BINS, *APP_BINS, *PARTITION_BIN, *JSON_METADATA]
16+
17+
# Default artifacts for tests that don't specify build version
18+
ALL_ARTIFACTS = ALL_ARTIFACTS_BUILDV1

tools/test_build_system/test_rebuild.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33
# These tests check whether the build system rebuilds some files or not
44
# depending on the changes to the project.
@@ -7,7 +7,8 @@
77
from pathlib import Path
88

99
import pytest
10-
from test_build_system_helpers import ALL_ARTIFACTS
10+
from test_build_system_helpers import ALL_ARTIFACTS_BUILDV1
11+
from test_build_system_helpers import ALL_ARTIFACTS_BUILDV2
1112
from test_build_system_helpers import APP_BINS
1213
from test_build_system_helpers import BOOTLOADER_BINS
1314
from test_build_system_helpers import PARTITION_BIN
@@ -17,7 +18,7 @@
1718

1819

1920
@pytest.mark.usefixtures('test_app_copy')
20-
def test_rebuild_no_changes(idf_py: IdfPyFunc) -> None:
21+
def test_rebuild_no_changes(idf_py: IdfPyFunc, request: pytest.FixtureRequest) -> None:
2122
logging.info('initial build')
2223
idf_py('build')
2324
logging.info('get the first snapshot')
@@ -32,7 +33,8 @@ def test_rebuild_no_changes(idf_py: IdfPyFunc) -> None:
3233
)
3334

3435
logging.info('check that all build artifacts were generated')
35-
for artifact in ALL_ARTIFACTS:
36+
all_artifacts = ALL_ARTIFACTS_BUILDV2 if request.config.getoption('buildv2', False) else ALL_ARTIFACTS_BUILDV1
37+
for artifact in all_artifacts:
3638
assert Path(artifact).exists()
3739

3840
logging.info('build again with no changes')

0 commit comments

Comments
 (0)