diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index af386b618a..09f458c64b 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -1,15 +1,22 @@ import esphome.codegen as cg -# Re-exported for the many esp32-side users; defined in esphome.const so -# the upload/logs fast path can read them without importing this package. +# Re-exported for the many esp32-side users; defined in esphome.const +# and esphome.espidf so the upload/logs fast path can use them without +# importing this package. from esphome.const import ( # noqa: F401 # pylint: disable=unused-import KEY_ESP32, + KEY_FLASH_SIZE, KEY_IDF_VERSION, KEY_VARIANT, ) +# Back compat for external components only; in-tree callers import it +# from esphome.espidf directly. +from esphome.espidf import ( # noqa: F401 # pylint: disable=unused-import + variant_to_idf_target, +) + KEY_BOARD = "board" -KEY_FLASH_SIZE = "flash_size" KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" KEY_COMPONENTS = "components" KEY_EXCLUDE_COMPONENTS = "exclude_components" @@ -69,9 +76,4 @@ VARIANT_FRIENDLY = { } -def variant_to_idf_target(variant: str) -> str: - """Map an esp32 variant name (e.g. "ESP32S3") to its ESP-IDF target name.""" - return variant.lower().replace("-", "") - - esp32_ns = cg.esphome_ns.namespace("esp32") diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 229ac61f24..3e89ab989f 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -1,4 +1,8 @@ import esphome.codegen as cg + +# Re-exported from the shared definition; here it indexes the BOARDS +# metadata dicts, whose entries in boards.py spell the literal. +from esphome.const import KEY_FLASH_SIZE # noqa: F401 # pylint: disable=unused-import from esphome.core import CORE KEY_ESP8266 = "esp8266" @@ -8,7 +12,6 @@ CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" CONF_ENABLE_SERIAL = "enable_serial" CONF_ENABLE_SERIAL1 = "enable_serial1" -KEY_FLASH_SIZE = "flash_size" KEY_WAVEFORM_REQUIRED = "waveform_required" KEY_SERIAL_REQUIRED = "serial_required" KEY_SERIAL1_REQUIRED = "serial1_required" diff --git a/esphome/const.py b/esphome/const.py index b1302af922..167176cf03 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1423,9 +1423,12 @@ KEY_NAME = "name" KEY_VARIANT = "variant" KEY_PAST_SAFE_MODE = "past_safe_mode" # esp32 storage keys; defined here so the upload/logs fast path -# (storage_json.apply_to_core) can use them without importing the -# esp32 component package. +# (storage_json.apply_to_core, espidf.toolchain) can use them without +# importing the esp32 component package. KEY_ESP32 = "esp32" +# Also used by esp8266 to index its BOARDS metadata dicts, whose +# entries in boards.py spell the literal; do not change the value. +KEY_FLASH_SIZE = "flash_size" KEY_IDF_VERSION = "idf_version" # Entity categories diff --git a/esphome/espidf/__init__.py b/esphome/espidf/__init__.py index e69de29bb2..079eede1f1 100644 --- a/esphome/espidf/__init__.py +++ b/esphome/espidf/__init__.py @@ -0,0 +1,11 @@ +"""ESP-IDF direct build support. + +Deliberately light: the upload fast path imports submodules of this +package without the esp32 component package, so nothing here may pull +in codegen or validation. +""" + + +def variant_to_idf_target(variant: str) -> str: + """Map an esp32 variant name (e.g. "ESP32S3") to its ESP-IDF target name.""" + return variant.lower().replace("-", "") diff --git a/esphome/espidf/component.py b/esphome/espidf/component.py index b22b39bf6d..aa6f10c261 100644 --- a/esphome/espidf/component.py +++ b/esphome/espidf/component.py @@ -12,6 +12,7 @@ import os from pathlib import Path from esphome.core import CORE, Library +from esphome.espidf import variant_to_idf_target from esphome.helpers import write_file_if_changed from esphome.platformio.library import ( DEFAULT_BUILD_FLAGS, @@ -54,7 +55,6 @@ def _apply_extra_script(component: IDFComponent) -> None: if not script_path.is_relative_to(library_root) or not script_path.is_file(): return from esphome.components.esp32 import get_esp32_variant - from esphome.components.esp32.const import variant_to_idf_target from esphome.espidf.extra_script import captured_as_build_flags, run_extra_script idf_target = variant_to_idf_target(get_esp32_variant()) diff --git a/esphome/espidf/toolchain.py b/esphome/espidf/toolchain.py index fd95805c6c..e1688f4170 100644 --- a/esphome/espidf/toolchain.py +++ b/esphome/espidf/toolchain.py @@ -9,20 +9,18 @@ import re import shutil import subprocess -from esphome.components.esp32.const import ( - KEY_ESP32, - KEY_FLASH_SIZE, - KEY_IDF_VERSION, - KEY_VARIANT, - variant_to_idf_target, -) from esphome.const import ( CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, CONF_FRAMEWORK, CONF_SOURCE, + KEY_ESP32, + KEY_FLASH_SIZE, + KEY_IDF_VERSION, + KEY_VARIANT, ) from esphome.core import CORE, EsphomeError +from esphome.espidf import variant_to_idf_target from esphome.espidf.framework import check_esp_idf_install, get_framework_env from esphome.espidf.size_summary import print_summary from esphome.helpers import add_git_ceiling_directory diff --git a/tests/unit_tests/test_lazy_imports.py b/tests/unit_tests/test_lazy_imports.py index 7eff5c9ef5..e9015e129f 100644 --- a/tests/unit_tests/test_lazy_imports.py +++ b/tests/unit_tests/test_lazy_imports.py @@ -121,3 +121,18 @@ def test_api_client_does_not_import_heavy_modules() -> None: "The logs fast path skips validation; importing the validation " "stack anyway defeats the validated-config cache." ) + + +def test_espidf_toolchain_does_not_import_heavy_modules() -> None: + """The esp-idf upload path must not pull the esp32 package back in. + + upload_using_esptool reaches espidf.toolchain for esp-idf builds; + its keys and the variant mapping live in esphome.const and + esphome.espidf precisely so this import stays light. + """ + leaked = _leaked_heavy_modules("esphome.espidf.toolchain") + assert not leaked, ( + f"esphome.espidf.toolchain imports heavy modules: {leaked}. " + "The upload fast path skips validation; importing the validation " + "stack anyway defeats the validated-config cache." + )