[esp32] Improve and simplify IDF component support (#9163)

This commit is contained in:
Jonathan Swoboda 2025-06-26 01:29:42 -04:00 committed by GitHub
parent 9daa9a6de8
commit 92365f133d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,7 +4,7 @@ import logging
import os import os
from pathlib import Path from pathlib import Path
from esphome import git from esphome import yaml_util
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@ -23,7 +23,6 @@ from esphome.const import (
CONF_REFRESH, CONF_REFRESH,
CONF_SOURCE, CONF_SOURCE,
CONF_TYPE, CONF_TYPE,
CONF_URL,
CONF_VARIANT, CONF_VARIANT,
CONF_VERSION, CONF_VERSION,
KEY_CORE, KEY_CORE,
@ -32,14 +31,13 @@ from esphome.const import (
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_ESP32, PLATFORM_ESP32,
TYPE_GIT,
TYPE_LOCAL,
__version__, __version__,
) )
from esphome.core import CORE, HexInt, TimePeriod from esphome.core import CORE, HexInt, TimePeriod
from esphome.cpp_generator import RawExpression from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType
from .boards import BOARDS from .boards import BOARDS
from .const import ( # noqa from .const import ( # noqa
@ -49,10 +47,8 @@ from .const import ( # noqa
KEY_EXTRA_BUILD_FILES, KEY_EXTRA_BUILD_FILES,
KEY_PATH, KEY_PATH,
KEY_REF, KEY_REF,
KEY_REFRESH,
KEY_REPO, KEY_REPO,
KEY_SDKCONFIG_OPTIONS, KEY_SDKCONFIG_OPTIONS,
KEY_SUBMODULES,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@ -235,7 +231,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component( def add_idf_component(
*, *,
name: str, name: str,
repo: str, repo: str = None,
ref: str = None, ref: str = None,
path: str = None, path: str = None,
refresh: TimePeriod = None, refresh: TimePeriod = None,
@ -245,30 +241,27 @@ def add_idf_component(
"""Add an esp-idf component to the project.""" """Add an esp-idf component to the project."""
if not CORE.using_esp_idf: if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project") raise ValueError("Not an esp-idf project")
if components is None: if not repo and not ref and not path:
components = [] raise ValueError("Requires at least one of repo, ref or path")
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]: if refresh or submodules or components:
_LOGGER.warning(
"The refresh, components and submodules parameters in add_idf_component() are "
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
"an issue to the external_component author and ask them to update it."
)
if components:
for comp in components:
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: f"{path}/{comp}" if path else comp,
}
else:
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
KEY_REPO: repo, KEY_REPO: repo,
KEY_REF: ref, KEY_REF: ref,
KEY_PATH: path, KEY_PATH: path,
KEY_REFRESH: refresh,
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
} }
else:
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
if components is not None:
component_config[KEY_COMPONENTS] = list(
set(component_config[KEY_COMPONENTS] + components)
)
if submodules is not None:
if component_config[KEY_SUBMODULES] is None:
component_config[KEY_SUBMODULES] = submodules
else:
component_config[KEY_SUBMODULES] = list(
set(component_config[KEY_SUBMODULES] + submodules)
)
def add_extra_script(stage: str, filename: str, path: str): def add_extra_script(stage: str, filename: str, path: str):
@ -575,6 +568,17 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
def _validate_idf_component(config: ConfigType) -> ConfigType:
"""Validate IDF component config and warn about deprecated options."""
if CONF_REFRESH in config:
_LOGGER.warning(
"The 'refresh' option for IDF components is deprecated and has no effect. "
"It will be removed in ESPHome 2026.1. Please remove it from your configuration."
)
return config
ESP_IDF_FRAMEWORK_SCHEMA = cv.All( ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@ -614,15 +618,19 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
} }
), ),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
cv.Schema( cv.All(
{ cv.Schema(
cv.Required(CONF_NAME): cv.string_strict, {
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA, cv.Required(CONF_NAME): cv.string_strict,
cv.Optional(CONF_PATH): cv.string, cv.Optional(CONF_SOURCE): cv.git_ref,
cv.Optional(CONF_REFRESH, default="1d"): cv.All( cv.Optional(CONF_REF): cv.string,
cv.string, cv.source_refresh cv.Optional(CONF_PATH): cv.string,
), cv.Optional(CONF_REFRESH): cv.All(
} cv.string, cv.source_refresh
),
}
),
_validate_idf_component,
) )
), ),
} }
@ -814,18 +822,12 @@ async def to_code(config):
add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
for component in conf[CONF_COMPONENTS]: for component in conf[CONF_COMPONENTS]:
source = component[CONF_SOURCE] add_idf_component(
if source[CONF_TYPE] == TYPE_GIT: name=component[CONF_NAME],
add_idf_component( repo=component.get(CONF_SOURCE),
name=component[CONF_NAME], ref=component.get(CONF_REF),
repo=source[CONF_URL], path=component.get(CONF_PATH),
ref=source.get(CONF_REF), )
path=component.get(CONF_PATH),
refresh=component[CONF_REFRESH],
)
elif source[CONF_TYPE] == TYPE_LOCAL:
_LOGGER.warning("Local components are not implemented yet.")
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option("framework", "arduino") cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ARDUINO")
@ -924,6 +926,26 @@ def _write_sdkconfig():
write_file_if_changed(sdk_path, contents) write_file_if_changed(sdk_path, contents)
def _write_idf_component_yml():
yml_path = Path(CORE.relative_build_path("src/idf_component.yml"))
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
dependencies = {}
for name, component in components.items():
dependency = {}
if component[KEY_REF]:
dependency["version"] = component[KEY_REF]
if component[KEY_REPO]:
dependency["git"] = component[KEY_REPO]
if component[KEY_PATH]:
dependency["path"] = component[KEY_PATH]
dependencies[name] = dependency
contents = yaml_util.dump({"dependencies": dependencies})
else:
contents = ""
write_file_if_changed(yml_path, contents)
# Called by writer.py # Called by writer.py
def copy_files(): def copy_files():
if CORE.using_arduino: if CORE.using_arduino:
@ -936,6 +958,7 @@ def copy_files():
) )
if CORE.using_esp_idf: if CORE.using_esp_idf:
_write_sdkconfig() _write_sdkconfig()
_write_idf_component_yml()
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
write_file_if_changed( write_file_if_changed(
CORE.relative_build_path("partitions.csv"), CORE.relative_build_path("partitions.csv"),
@ -952,55 +975,6 @@ def copy_files():
__version__, __version__,
) )
import shutil
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
for name, component in components.items():
repo_dir, _ = git.clone_or_update(
url=component[KEY_REPO],
ref=component[KEY_REF],
refresh=component[KEY_REFRESH],
domain="idf_components",
submodules=component[KEY_SUBMODULES],
)
mkdir_p(CORE.relative_build_path("components"))
component_dir = repo_dir
if component[KEY_PATH] is not None:
component_dir = component_dir / component[KEY_PATH]
if component[KEY_COMPONENTS] == ["*"]:
shutil.copytree(
component_dir,
CORE.relative_build_path("components"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
elif len(component[KEY_COMPONENTS]) > 0:
for comp in component[KEY_COMPONENTS]:
shutil.copytree(
component_dir / comp,
CORE.relative_build_path(f"components/{comp}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
else:
shutil.copytree(
component_dir,
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items(): for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
if file[KEY_PATH].startswith("http"): if file[KEY_PATH].startswith("http"):
import requests import requests