From 3f615f87cf88f44878b1bfe7ea85d3ca5a1396bf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Aug 2026 01:16:32 -0500 Subject: [PATCH] [esp32] Exclude esp_http_server and nvs_sec_provider from IDF builds by default (#18748) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 16 ++++++++++ .../esp32_camera_web_server/__init__.py | 4 +++ esphome/components/web_server_idf/__init__.py | 1 + ...xclusion_reincludes_camera_web_server.yaml | 15 ++++++++++ .../exclusion_reincludes_nvs_sdkconfig.yaml | 11 +++++++ .../exclusion_stays_nvs_sdkconfig_off.yaml | 9 ++++++ tests/component_tests/esp32/test_esp32.py | 30 ++++++++++++++++++- 7 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/component_tests/esp32/config/exclusion_reincludes_camera_web_server.yaml create mode 100644 tests/component_tests/esp32/config/exclusion_reincludes_nvs_sdkconfig.yaml create mode 100644 tests/component_tests/esp32/config/exclusion_stays_nvs_sdkconfig_off.yaml diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index fca63e9a25..073d87402a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -238,6 +238,7 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = ( "esp_gdbstub", # GDB stub panic handler - unused by ESPHome; bt pulls it back "esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality "esp_http_client", # HTTP client - only needed by http_request component + "esp_http_server", # HTTP server - re-included by web_server_idf, esp32_camera_web_server "esp_https_ota", # ESP-IDF HTTPS OTA - ESPHome has its own OTA implementation "esp_https_server", # HTTPS server - ESPHome has its own web server "esp_lcd", # LCD controller drivers - only needed by display component @@ -246,6 +247,7 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = ( "fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage "json", # cJSON library - ESPHome uses ArduinoJson instead "mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation + "nvs_sec_provider", # NVS encryption key provider - re-included when CONFIG_NVS_ENCRYPTION is set "openthread", # Thread protocol - only needed by openthread component "perfmon", # Xtensa performance monitor - ESPHome has its own debug component "protobuf-c", # Protobuf runtime - only used by provisioning components (also excluded) @@ -649,6 +651,16 @@ class RawSdkconfigValue: SdkconfigValueType = bool | int | HexInt | str | RawSdkconfigValue +def is_idf_sdkconfig_option_enabled(name: str) -> bool: + """Return True when a bool sdkconfig option resolves to ``y``. + + Handles both the ``True`` a component sets and the raw ``y`` a user sets + in ``sdkconfig_options``. + """ + value = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS].get(name) + return value is not None and _format_sdkconfig_val(value) == "y" + + def set_idf_sdkconfig_default(name: str, value: SdkconfigValueType) -> None: """Set an sdkconfig option unless it is already set. @@ -2191,6 +2203,10 @@ def register_exclude_components_cmake_arg() -> None: @coroutine_with_priority(CoroPriority.FINAL) async def _write_exclude_components() -> None: """Write EXCLUDE_COMPONENTS cmake arg after all components have registered exclusions.""" + # NVS encryption needs nvs_sec_provider however it was enabled: the + # nvs_encryption option, raw sdkconfig_options or another component. + if is_idf_sdkconfig_option_enabled("CONFIG_NVS_ENCRYPTION"): + include_builtin_idf_component("nvs_sec_provider") register_exclude_components_cmake_arg() diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py index 55ace66681..d54d5c6937 100644 --- a/esphome/components/esp32_camera_web_server/__init__.py +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components.esp32 import include_builtin_idf_component import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODE, CONF_PORT from esphome.types import ConfigType @@ -35,6 +36,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MODE): cv.enum(MODES, upper=True), }, ).extend(cv.COMPONENT_SCHEMA), + cv.only_on_esp32, _consume_camera_web_server_sockets, ) @@ -44,3 +46,5 @@ async def to_code(config: ConfigType) -> None: cg.add(server.set_port(config[CONF_PORT])) cg.add(server.set_mode(config[CONF_MODE])) await cg.register_component(server, config) + # esp_http_server is excluded from IDF builds by default to save compile time + include_builtin_idf_component("esp_http_server") diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py index c16b0a2833..5a400dfbf3 100644 --- a/esphome/components/web_server_idf/__init__.py +++ b/esphome/components/web_server_idf/__init__.py @@ -20,6 +20,7 @@ async def to_code(config: ConfigType) -> None: # Re-enable esp-tls (excluded by default to save compile time); # web_server_idf.cpp includes for digest auth include_builtin_idf_component("esp-tls") + include_builtin_idf_component("esp_http_server") # multipart.cpp is fully #ifdef'd on USE_WEBSERVER_OTA (set by the diff --git a/tests/component_tests/esp32/config/exclusion_reincludes_camera_web_server.yaml b/tests/component_tests/esp32/config/exclusion_reincludes_camera_web_server.yaml new file mode 100644 index 0000000000..1bc5c936e0 --- /dev/null +++ b/tests/component_tests/esp32/config/exclusion_reincludes_camera_web_server.yaml @@ -0,0 +1,15 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + +wifi: + ssid: "test_ssid" + password: "test_password" + +esp32_camera_web_server: + port: 8080 + mode: stream diff --git a/tests/component_tests/esp32/config/exclusion_reincludes_nvs_sdkconfig.yaml b/tests/component_tests/esp32/config/exclusion_reincludes_nvs_sdkconfig.yaml new file mode 100644 index 0000000000..a82d4aaba8 --- /dev/null +++ b/tests/component_tests/esp32/config/exclusion_reincludes_nvs_sdkconfig.yaml @@ -0,0 +1,11 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + sdkconfig_options: + CONFIG_NVS_ENCRYPTION: y + CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC: y + CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID: "0" diff --git a/tests/component_tests/esp32/config/exclusion_stays_nvs_sdkconfig_off.yaml b/tests/component_tests/esp32/config/exclusion_stays_nvs_sdkconfig_off.yaml new file mode 100644 index 0000000000..2580e77736 --- /dev/null +++ b/tests/component_tests/esp32/config/exclusion_stays_nvs_sdkconfig_off.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + sdkconfig_options: + CONFIG_NVS_ENCRYPTION: n diff --git a/tests/component_tests/esp32/test_esp32.py b/tests/component_tests/esp32/test_esp32.py index 9925887c66..0ffbe16a17 100644 --- a/tests/component_tests/esp32/test_esp32.py +++ b/tests/component_tests/esp32/test_esp32.py @@ -261,9 +261,24 @@ def test_esp32_configuration_errors( ), pytest.param( "exclusion_reincludes_web_server.yaml", - ("esp-tls",), + ("esp-tls", "esp_http_server"), id="web_server_idf", ), + pytest.param( + "nvs_encryption_s3.yaml", + ("nvs_sec_provider",), + id="nvs_encryption", + ), + pytest.param( + "exclusion_reincludes_nvs_sdkconfig.yaml", + ("nvs_sec_provider",), + id="nvs_encryption_raw_sdkconfig", + ), + pytest.param( + "exclusion_reincludes_camera_web_server.yaml", + ("esp_http_server",), + id="esp32_camera_web_server", + ), pytest.param( "exclusion_reincludes_nextion.yaml", ("esp-tls", "esp_http_client"), @@ -291,6 +306,19 @@ def test_default_exclusions_reincluded_by_owning_components( # Components no part of this config touches stay excluded. assert "unity" in excluded assert "fatfs" in excluded + # The HTTP server only comes back for configs that run one. + assert ("esp_http_server" in excluded) == ("esp_http_server" not in reincluded) + + +def test_nvs_sec_provider_stays_excluded_when_encryption_is_off( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """An explicit CONFIG_NVS_ENCRYPTION=n keeps nvs_sec_provider excluded.""" + from esphome.components.esp32.const import KEY_EXCLUDE_COMPONENTS + + generate_main(component_config_path("exclusion_stays_nvs_sdkconfig_off.yaml")) + assert "nvs_sec_provider" in CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] _BUNDLE_OPTIONS = (