From d4d993c8185e4887d4c3a828f29c5b1dd1ef569d Mon Sep 17 00:00:00 2001 From: "igor.masar" Date: Thu, 30 Apr 2026 14:54:11 +0200 Subject: [PATCH 1/2] feat(soc): add USB DFU and OTG console caps Add USB DFU and OTG console SOC capability flags for esp32s2, esp32s3, and esp32p4. Use these caps in Kconfig, documentation conditionals, and idf.py DFU actions so USB support is derived from SOC capabilities instead of hardcoded target names. --- components/esp_stdio/Kconfig | 2 +- .../esp32p4/include/soc/Kconfig.soc_caps.in | 4 ++ components/soc/esp32p4/include/soc/soc_caps.h | 1 + .../esp32s2/include/soc/Kconfig.soc_caps.in | 8 ++++ components/soc/esp32s2/include/soc/soc_caps.h | 2 + .../esp32s3/include/soc/Kconfig.soc_caps.in | 8 ++++ components/soc/esp32s3/include/soc/soc_caps.h | 2 + docs/conf_common.py | 44 ++++++++----------- docs/en/api-guides/index.rst | 4 +- docs/en/api-guides/stdio.rst | 8 ++-- docs/zh_CN/api-guides/index.rst | 4 +- docs/zh_CN/api-guides/stdio.rst | 8 ++-- tools/idf_py_actions/dfu_ext.py | 33 ++++++++------ 13 files changed, 76 insertions(+), 52 deletions(-) diff --git a/components/esp_stdio/Kconfig b/components/esp_stdio/Kconfig index a103ba0c526..ba02abe8459 100644 --- a/components/esp_stdio/Kconfig +++ b/components/esp_stdio/Kconfig @@ -22,7 +22,7 @@ menu "ESP-STDIO" config ESP_CONSOLE_USB_CDC bool "USB CDC" # && !TINY_USB is because the ROM CDC driver is currently incompatible with TinyUSB. - depends on (IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3) && !TINY_USB + depends on SOC_USB_OTG_CONSOLE_SUPPORTED && !TINY_USB config ESP_CONSOLE_USB_SERIAL_JTAG bool "USB Serial/JTAG Controller" depends on SOC_USB_SERIAL_JTAG_SUPPORTED diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 9770b1a1104..1984cc20f55 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -111,6 +111,10 @@ config SOC_USB_OTG_SUPPORTED bool default y +config SOC_USB_DFU_SUPPORTED + bool + default y + config SOC_WIRELESS_HOST_SUPPORTED bool default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 9f5caac5a3c..4bf02637fd2 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -50,6 +50,7 @@ #define SOC_ASYNC_MEMCPY_SUPPORTED 1 #define SOC_EMAC_SUPPORTED 1 #define SOC_USB_OTG_SUPPORTED 1 +#define SOC_USB_DFU_SUPPORTED 1 #define SOC_WIRELESS_HOST_SUPPORTED 1 #define SOC_USB_SERIAL_JTAG_SUPPORTED 1 #define SOC_TEMP_SENSOR_SUPPORTED 1 diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index 87efef0536c..9e8601bc397 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -47,6 +47,14 @@ config SOC_USB_OTG_SUPPORTED bool default y +config SOC_USB_DFU_SUPPORTED + bool + default y + +config SOC_USB_OTG_CONSOLE_SUPPORTED + bool + default y + config SOC_PCNT_SUPPORTED bool default y diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index 631f7aff845..6d0bacb51c0 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -54,6 +54,8 @@ #define SOC_ULP_FSM_SUPPORTED 1 #define SOC_RISCV_COPROC_SUPPORTED 1 #define SOC_USB_OTG_SUPPORTED 1 +#define SOC_USB_DFU_SUPPORTED 1 +#define SOC_USB_OTG_CONSOLE_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1 #define SOC_PHY_SUPPORTED 1 #define SOC_WIFI_SUPPORTED 1 diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index fe0a48c1b46..6b1198494ed 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -95,6 +95,14 @@ config SOC_USB_OTG_SUPPORTED bool default y +config SOC_USB_DFU_SUPPORTED + bool + default y + +config SOC_USB_OTG_CONSOLE_SUPPORTED + bool + default y + config SOC_USB_SERIAL_JTAG_SUPPORTED bool default y diff --git a/components/soc/esp32s3/include/soc/soc_caps.h b/components/soc/esp32s3/include/soc/soc_caps.h index 228893254ed..52481bfb2d2 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -51,6 +51,8 @@ #define SOC_RISCV_COPROC_SUPPORTED 1 #define SOC_BT_SUPPORTED 1 #define SOC_USB_OTG_SUPPORTED 1 +#define SOC_USB_DFU_SUPPORTED 1 +#define SOC_USB_OTG_CONSOLE_SUPPORTED 1 #define SOC_USB_SERIAL_JTAG_SUPPORTED 1 #define SOC_CCOMP_TIMER_SUPPORTED 1 #define SOC_ASYNC_MEMCPY_SUPPORTED 1 diff --git a/docs/conf_common.py b/docs/conf_common.py index 21c519ebb66..26cf33d0ec4 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -278,32 +278,22 @@ ESP32_DOCS = ( + QEMU_DOCS ) -ESP32S2_DOCS = ( - [ - 'hw-reference/esp32s2/**', - 'api-guides/usb-console.rst', - 'api-reference/peripherals/ds.rst', - 'api-guides/RF_calibration.rst', - 'api-guides/phy.rst', - ] - + FTDI_JTAG_DOCS - + USB_OTG_DFU_DOCS - + USB_OTG_CONSOLE_DOCS -) +ESP32S2_DOCS = [ + 'hw-reference/esp32s2/**', + 'api-guides/usb-console.rst', + 'api-reference/peripherals/ds.rst', + 'api-guides/RF_calibration.rst', + 'api-guides/phy.rst', +] + FTDI_JTAG_DOCS -ESP32S3_DOCS = ( - [ - 'hw-reference/esp32s3/**', - 'api-reference/system/ipc.rst', - 'api-guides/flash_psram_config.rst', - 'api-reference/peripherals/sd_pullup_requirements.rst', - 'api-guides/RF_calibration.rst', - 'api-guides/phy.rst', - ] - + USB_OTG_DFU_DOCS - + USB_OTG_CONSOLE_DOCS - + QEMU_DOCS -) +ESP32S3_DOCS = [ + 'hw-reference/esp32s3/**', + 'api-reference/system/ipc.rst', + 'api-guides/flash_psram_config.rst', + 'api-reference/peripherals/sd_pullup_requirements.rst', + 'api-guides/RF_calibration.rst', + 'api-guides/phy.rst', +] + QEMU_DOCS # No JTAG docs for this one as it gets gated on SOC_USB_SERIAL_JTAG_SUPPORTED down below. ESP32C3_DOCS = ['hw-reference/esp32c3/**', 'api-guides/RF_calibration.rst', 'api-guides/phy.rst'] + QEMU_DOCS @@ -349,7 +339,7 @@ ESP32P4_DOCS = [ 'api-reference/system/ipc.rst', 'api-reference/peripherals/cap_touch_sens.rst', 'api-reference/peripherals/sd_pullup_requirements.rst', -] + USB_OTG_DFU_DOCS +] # format: {tag needed to include: documents to included}, tags are parsed from sdkconfig and peripheral_caps.h headers conditional_include_dict = { @@ -371,6 +361,8 @@ conditional_include_dict = { 'SOC_SDMMC_HOST_SUPPORTED': SDMMC_DOCS, 'SOC_SDIO_SLAVE_SUPPORTED': SDIO_SLAVE_DOCS, 'SOC_MCPWM_SUPPORTED': MCPWM_DOCS, + 'SOC_USB_DFU_SUPPORTED': USB_OTG_DFU_DOCS, + 'SOC_USB_OTG_CONSOLE_SUPPORTED': USB_OTG_CONSOLE_DOCS, 'SOC_USB_SERIAL_JTAG_SUPPORTED': USB_SERIAL_JTAG_DOCS, 'SOC_DEDICATED_GPIO_SUPPORTED': DEDIC_GPIO_DOCS, 'SOC_LCD_I80_SUPPORTED': I80_LCD_DOCS, diff --git a/docs/en/api-guides/index.rst b/docs/en/api-guides/index.rst index 7abc2cf7839..84f3a7afb21 100644 --- a/docs/en/api-guides/index.rst +++ b/docs/en/api-guides/index.rst @@ -21,7 +21,7 @@ API Guides core_dump current-consumption-measurement-modules :ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB: deep-sleep-stub - :SOC_USB_OTG_SUPPORTED and not esp32h4 and not esp32s31: dfu + :SOC_USB_DFU_SUPPORTED: dfu error-handling :SOC_WIFI_MESH_SUPPORT: esp-wifi-mesh :SOC_SPIRAM_SUPPORTED: external-ram @@ -46,7 +46,7 @@ API Guides tools/index unit-tests host-apps - :SOC_USB_OTG_SUPPORTED and not esp32p4 and not esp32h4 and not esp32s31: usb-otg-console + :SOC_USB_OTG_CONSOLE_SUPPORTED: usb-otg-console :SOC_USB_SERIAL_JTAG_SUPPORTED: usb-serial-jtag-console :SOC_WIFI_SUPPORTED: wifi-driver/index :SOC_WIFI_SUPPORTED: wifi-security diff --git a/docs/en/api-guides/stdio.rst b/docs/en/api-guides/stdio.rst index f9c3bc0aa21..304436c05f3 100644 --- a/docs/en/api-guides/stdio.rst +++ b/docs/en/api-guides/stdio.rst @@ -21,7 +21,7 @@ On {IDF_TARGET_NAME}, ESP-IDF provides implementations of VFS drivers for I/O ov - UART :SOC_USB_SERIAL_JTAG_SUPPORTED: - USB Serial/JTAG - :esp32s2 or esp32s3: - USB CDC (using USB_OTG peripheral) + :SOC_USB_OTG_CONSOLE_SUPPORTED: - USB CDC (using USB_OTG peripheral) - "Null" (no output) Standard I/O is not limited to these options, though. See below on enabling custom destinations for standard I/O. @@ -35,7 +35,7 @@ Built-in implementations of standard I/O can be selected using several Kconfig o - :ref:`CONFIG_ESP_CONSOLE_UART_DEFAULT` — Enables UART with default options (pin numbers, baud rate) for standard I/O. - :ref:`CONFIG_ESP_CONSOLE_UART_CUSTOM` — Enables UART for standard I/O, with TX/RX pin numbers and baud rate configurable via Kconfig. - :esp32s2 or esp32s3: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC` — Enables USB CDC (using USB_OTG peripheral) for standard I/O. See :doc:`usb-otg-console` for details about hardware connections required. + :SOC_USB_OTG_CONSOLE_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC` — Enables USB CDC (using USB_OTG peripheral) for standard I/O. See :doc:`usb-otg-console` for details about hardware connections required. :SOC_USB_SERIAL_JTAG_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG` — Enables USB Serial/JTAG for standard I/O. See :doc:`usb-serial-jtag-console` for details about hardware connections required. - :ref:`CONFIG_ESP_CONSOLE_NONE` — Disables standard I/O. If this option is selected, ``stdin``, ``stdout``, and ``stderr`` will be mapped to ``/dev/null`` and won't produce any output or generate any input. @@ -102,7 +102,7 @@ When the interrupt-driven driver is installed, it is also possible to enable/dis When the interrupt-driven driver is installed, it is also possible to enable/disable non-blocking behavior using ``fcntl`` function with ``O_NONBLOCK`` flag. -.. only:: esp32s2 or esp32s3 +.. only:: SOC_USB_OTG_CONSOLE_SUPPORTED USB CDC (using USB_OTG peripheral) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -132,7 +132,7 @@ It is also possible to configure line ending conversion for the specific VFS dri - For UART: :cpp:func:`uart_vfs_dev_port_set_rx_line_endings` and :cpp:func:`uart_vfs_dev_port_set_tx_line_endings` :SOC_USB_SERIAL_JTAG_SUPPORTED: - For USB Serial/JTAG: :cpp:func:`usb_serial_jtag_vfs_set_rx_line_endings` and :cpp:func:`usb_serial_jtag_vfs_set_tx_line_endings` - :esp32s2 or esp32s3: - For USB CDC (using USB_OTG peripheral): :cpp:func:`esp_vfs_dev_cdcacm_set_rx_line_endings` and :cpp:func:`esp_vfs_dev_cdcacm_set_tx_line_endings` + :SOC_USB_OTG_CONSOLE_SUPPORTED: - For USB CDC (using USB_OTG peripheral): :cpp:func:`esp_vfs_dev_cdcacm_set_rx_line_endings` and :cpp:func:`esp_vfs_dev_cdcacm_set_tx_line_endings` Buffering --------- diff --git a/docs/zh_CN/api-guides/index.rst b/docs/zh_CN/api-guides/index.rst index 4e0cf1ccad2..5dcfb717610 100644 --- a/docs/zh_CN/api-guides/index.rst +++ b/docs/zh_CN/api-guides/index.rst @@ -21,7 +21,7 @@ API 指南 core_dump current-consumption-measurement-modules :ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB: deep-sleep-stub - :SOC_USB_OTG_SUPPORTED and not esp32h4 and not esp32s31: dfu + :SOC_USB_DFU_SUPPORTED: dfu error-handling :SOC_WIFI_MESH_SUPPORT: esp-wifi-mesh :SOC_SPIRAM_SUPPORTED: external-ram @@ -46,7 +46,7 @@ API 指南 tools/index unit-tests host-apps - :SOC_USB_OTG_SUPPORTED and not esp32p4 and not esp32h4 and not esp32s31: usb-otg-console + :SOC_USB_OTG_CONSOLE_SUPPORTED: usb-otg-console :SOC_USB_SERIAL_JTAG_SUPPORTED: usb-serial-jtag-console :SOC_WIFI_SUPPORTED: wifi-driver/index :SOC_WIFI_SUPPORTED: wifi-security diff --git a/docs/zh_CN/api-guides/stdio.rst b/docs/zh_CN/api-guides/stdio.rst index 3e3b2d7da3d..71642226849 100644 --- a/docs/zh_CN/api-guides/stdio.rst +++ b/docs/zh_CN/api-guides/stdio.rst @@ -21,7 +21,7 @@ ESP-IDF 提供了 C 标准输入输出功能,如 ``stdin``、``stdout`` 和 `` - UART :SOC_USB_SERIAL_JTAG_SUPPORTED: - USB Serial/JTAG - :esp32s2 or esp32s3: - USB CDC(使用 USB_OTG 外设) + :SOC_USB_OTG_CONSOLE_SUPPORTED: - USB CDC(使用 USB_OTG 外设) - "Null"(无输出) 不过,标准 I/O 并不限于这些选项。请参见下文,了解如何为标准 I/O 启用自定义目标。 @@ -35,7 +35,7 @@ ESP-IDF 提供了 C 标准输入输出功能,如 ``stdin``、``stdout`` 和 `` - :ref:`CONFIG_ESP_CONSOLE_UART_DEFAULT` — 启用 UART 用于标准 I/O,保持默认选项项(管脚号、波特率)。 - :ref:`CONFIG_ESP_CONSOLE_UART_CUSTOM` — 启用 UART 用于标准 I/O,通过 Kconfig 配置 TX/RX 管脚号和波特率。 - :esp32s2 or esp32s3: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC` — 启用 USB CDC(使用 USB_OTG 外设)用于标准 I/O。硬件连接要求请参见 :doc:`usb-otg-console`。 + :SOC_USB_OTG_CONSOLE_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC` — 启用 USB CDC(使用 USB_OTG 外设)用于标准 I/O。硬件连接要求请参见 :doc:`usb-otg-console`。 :SOC_USB_SERIAL_JTAG_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG` — 启用 USB Serial/JTAG 用于标准 I/O。硬件连接要求请参见 :doc:`usb-serial-jtag-console`。 - :ref:`CONFIG_ESP_CONSOLE_NONE` — 禁用标准 I/O。选择此选项时, ``stdin``、 ``stdout`` 和 ``stderr`` 将映射到 ``/dev/null``,不会产生输出或输入。 @@ -102,7 +102,7 @@ UART 安装中断驱动后,也可使用带有 ``O_NONBLOCK`` 标志的 ``fcntl`` 函数启用/禁用非阻塞行为。 -.. only:: esp32s2 or esp32s3 +.. only:: SOC_USB_OTG_CONSOLE_SUPPORTED USB CDC(使用 USB_OTG 外设) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -132,7 +132,7 @@ UART - 对于 UART::cpp:func:`uart_vfs_dev_port_set_rx_line_endings` 和 :cpp:func:`uart_vfs_dev_port_set_tx_line_endings` :SOC_USB_SERIAL_JTAG_SUPPORTED: - 对于 USB Serial/JTAG::cpp:func:`usb_serial_jtag_vfs_set_rx_line_endings` 和 :cpp:func:`usb_serial_jtag_vfs_set_tx_line_endings` - :esp32s2 or esp32s3: - 对于 USB CDC(使用 USB_OTG 外设)::cpp:func:`esp_vfs_dev_cdcacm_set_rx_line_endings` 和 :cpp:func:`esp_vfs_dev_cdcacm_set_tx_line_endings` + :SOC_USB_OTG_CONSOLE_SUPPORTED: - 对于 USB CDC(使用 USB_OTG 外设)::cpp:func:`esp_vfs_dev_cdcacm_set_rx_line_endings` 和 :cpp:func:`esp_vfs_dev_cdcacm_set_tx_line_endings` 缓冲 ----- diff --git a/tools/idf_py_actions/dfu_ext.py b/tools/idf_py_actions/dfu_ext.py index 4c72f6db829..90cc39a0731 100644 --- a/tools/idf_py_actions/dfu_ext.py +++ b/tools/idf_py_actions/dfu_ext.py @@ -1,19 +1,24 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -from typing import Dict + +import os from click.core import Context + from idf_py_actions.errors import FatalError -from idf_py_actions.tools import ensure_build_directory -from idf_py_actions.tools import is_target_supported from idf_py_actions.tools import PropertyDict +from idf_py_actions.tools import ensure_build_directory +from idf_py_actions.tools import get_sdkconfig_value from idf_py_actions.tools import run_target +SOC_USB_DFU_SUPPORTED = 'CONFIG_SOC_USB_DFU_SUPPORTED' -def action_extensions(base_actions: Dict, project_path: str) -> Dict: - SUPPORTED_TARGETS = ['esp32s2', 'esp32s3', 'esp32p4'] +def _soc_usb_dfu_supported(project_path: str) -> bool: + return bool(get_sdkconfig_value(os.path.join(project_path, 'sdkconfig'), SOC_USB_DFU_SUPPORTED) == 'y') + +def action_extensions(base_actions: dict, project_path: str) -> dict: def dfu_target(target_name: str, ctx: Context, args: PropertyDict, part_size: str) -> None: ensure_build_directory(args, ctx.info_name) run_target(target_name, args, {'ESP_DFU_PART_SIZE': part_size} if part_size else {}) @@ -29,8 +34,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: run_target(target_name, args, {'ESP_DFU_PATH': path}) except FatalError: # Cannot capture the error from dfu-util here so the best advise is: - print('Please have a look at the "Device Firmware Upgrade through USB" chapter in API Guides of the ' - 'ESP-IDF documentation for solving common dfu-util issues.') + print( + 'Please have a look at the "Device Firmware Upgrade through USB" chapter in API Guides of the ' + 'ESP-IDF documentation for solving common dfu-util issues.' + ) raise dfu_actions = { @@ -43,8 +50,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: { 'names': ['--part-size'], 'help': 'Large files are split up into smaller partitions in order to avoid timeout during ' - 'erasing flash. This option allows to overwrite the default partition size of ' - 'mkdfu.py.' + 'erasing flash. This option allows to overwrite the default partition size of ' + 'mkdfu.py.', } ], }, @@ -62,12 +69,12 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: 'names': ['--path'], 'default': '', 'help': 'Specify path to DFU device. The default empty path works if there is just one ' - 'ESP device with the same product identifier. See the device list for paths ' - 'of available devices.' + 'ESP device with the same product identifier. See the device list for paths ' + 'of available devices.', } ], }, } } - return dfu_actions if is_target_supported(project_path, SUPPORTED_TARGETS) else {} + return dfu_actions if _soc_usb_dfu_supported(project_path) else {} From 232035071e35c470fd4431677b2a96526f76ded7 Mon Sep 17 00:00:00 2001 From: Tomas Rezucha Date: Wed, 27 May 2026 11:37:45 +0200 Subject: [PATCH 2/2] feat(tools): Add 'check' field to idf.py actions --- tools/idf.py | 16 ++++++++++++++++ tools/idf_py_actions/dfu_ext.py | 22 +++++++++++++++------- tools/idf_py_actions/tools.py | 9 +-------- tools/test_build_system/test_build.py | 12 +++++++++--- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/tools/idf.py b/tools/idf.py index 7e0cb033d48..07271dcd01e 100755 --- a/tools/idf.py +++ b/tools/idf.py @@ -249,6 +249,7 @@ def init_cli(verbose_output: list | None = None) -> Any: def __init__( self, callback: Callable, + check: Callable | None, name: str, aliases: list, dependencies: list | None, @@ -256,6 +257,7 @@ def init_cli(verbose_output: list | None = None) -> Any: action_args: dict, ) -> None: self.callback = callback + self.check = check self.name = name self.dependencies = dependencies self.order_dependencies = order_dependencies @@ -270,6 +272,15 @@ def init_cli(verbose_output: list | None = None) -> Any: self.callback(self.name, context, global_args, **action_args) + def check_requirements( + self, context: click.core.Context, global_args: PropertyDict, action_args: dict | None = None + ) -> bool: + if self.check is None: + return True + if action_args is None: + action_args = self.action_args + return bool(self.check(self.name, context, global_args, **action_args)) + class Action(click.Command): callback: Callable @@ -280,6 +291,7 @@ def init_cli(verbose_output: list | None = None) -> Any: deprecated: dict | str | bool = False, dependencies: list | None = None, order_dependencies: list | None = None, + check: Callable | None = None, hidden: bool = False, **kwargs: Any, ) -> None: @@ -322,6 +334,7 @@ def init_cli(verbose_output: list | None = None) -> Any: def wrapped_callback(**action_args: Any) -> Task: return Task( callback=self.unwrapped_callback, + check=check, name=self.name, dependencies=dependencies, order_dependencies=order_dependencies, @@ -692,6 +705,9 @@ def init_cli(verbose_output: list | None = None) -> Any: dependecies_processed = True + if not task.check_requirements(ctx, global_args, task.action_args): + raise FatalError(f'Action "{task.name}" cannot run in the current project configuration.') + # If task have some dependencies they have to be executed before the task. for dep in task.dependencies: if dep not in tasks_to_run.keys(): diff --git a/tools/idf_py_actions/dfu_ext.py b/tools/idf_py_actions/dfu_ext.py index 90cc39a0731..0a86f3b5956 100644 --- a/tools/idf_py_actions/dfu_ext.py +++ b/tools/idf_py_actions/dfu_ext.py @@ -1,24 +1,29 @@ # SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -import os - from click.core import Context from idf_py_actions.errors import FatalError from idf_py_actions.tools import PropertyDict from idf_py_actions.tools import ensure_build_directory +from idf_py_actions.tools import get_sdkconfig_filename from idf_py_actions.tools import get_sdkconfig_value +from idf_py_actions.tools import red_print from idf_py_actions.tools import run_target SOC_USB_DFU_SUPPORTED = 'CONFIG_SOC_USB_DFU_SUPPORTED' -def _soc_usb_dfu_supported(project_path: str) -> bool: - return bool(get_sdkconfig_value(os.path.join(project_path, 'sdkconfig'), SOC_USB_DFU_SUPPORTED) == 'y') - - def action_extensions(base_actions: dict, project_path: str) -> dict: + def check_dfu_supported(target_name: str, ctx: Context, args: PropertyDict, **kwargs: str) -> bool: + sdkconfig_path = get_sdkconfig_filename(args) + if get_sdkconfig_value(sdkconfig_path, SOC_USB_DFU_SUPPORTED) == 'y': + return True + + target = get_sdkconfig_value(sdkconfig_path, 'CONFIG_IDF_TARGET') or 'unknown' + red_print(f'DFU is not supported for this target: {target}') + return False + def dfu_target(target_name: str, ctx: Context, args: PropertyDict, part_size: str) -> None: ensure_build_directory(args, ctx.info_name) run_target(target_name, args, {'ESP_DFU_PART_SIZE': part_size} if part_size else {}) @@ -44,6 +49,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: 'actions': { 'dfu': { 'callback': dfu_target, + 'check': check_dfu_supported, 'short_help': 'Build the DFU binary', 'dependencies': ['all'], 'options': [ @@ -57,11 +63,13 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: }, 'dfu-list': { 'callback': dfu_list_target, + 'check': check_dfu_supported, 'short_help': 'List DFU capable devices', 'dependencies': [], }, 'dfu-flash': { 'callback': dfu_flash_target, + 'check': check_dfu_supported, 'short_help': 'Flash the DFU binary', 'order_dependencies': ['dfu'], 'options': [ @@ -77,4 +85,4 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: } } - return dfu_actions if _soc_usb_dfu_supported(project_path) else {} + return dfu_actions diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index 8ab06824cf1..d9e4daceec2 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -982,19 +982,12 @@ def get_sdkconfig_value(sdkconfig_file: str, key: str) -> str | None: pattern = re.compile(rf'^{key}=\"?([^\"]*)\"?$') with open(sdkconfig_file, encoding='utf-8') as f: for line in f: - match = re.match(pattern, line) + match = re.match(pattern, line.strip()) if match: value = match.group(1) return value -def is_target_supported(project_path: str, supported_targets: list) -> bool: - """ - Returns True if the active target is supported, or False otherwise. - """ - return get_target(project_path) in supported_targets - - def _check_idf_target( args: 'PropertyDict', prog_name: str, cache: dict, cache_cmdl: dict, env: dict | None = None ) -> None: diff --git a/tools/test_build_system/test_build.py b/tools/test_build_system/test_build.py index 38aaba37c6a..1fc1a20f674 100644 --- a/tools/test_build_system/test_build.py +++ b/tools/test_build_system/test_build.py @@ -175,11 +175,17 @@ def test_build_fail_on_build_time(idf_py: IdfPyFunc, test_app_copy: Path) -> Non @pytest.mark.usefixtures('test_app_copy') def test_build_dfu(idf_py: IdfPyFunc) -> None: - logging.info('DFU build works') + logging.info('DFU is rejected for targets without USB DFU support') + idf_py('set-target', 'esp32') ret = idf_py('dfu', check=False) - assert 'command "dfu" is not known to idf.py and is not a Ninja target' in ret.stderr, ( - 'DFU build should fail for default chip target' + assert ret.returncode != 0 + assert 'DFU is not supported for this target: esp32' in ret.stderr + assert 'Action "dfu" cannot run in the current project configuration.' in ret.stderr + assert 'Adding "dfu"\'s dependency "all"' not in ret.stdout, ( + 'DFU check must fail before the "all" dependency is scheduled' ) + + logging.info('DFU build works for esp32s2') idf_py('set-target', 'esp32s2') ret = idf_py('dfu') assert 'build/dfu.bin" has been written. You may proceed with DFU flashing.' in ret.stdout, (