[core] add support for custom platform (#7616)

Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
This commit is contained in:
tomaszduda23 2025-01-23 00:06:07 +01:00 committed by GitHub
parent 5a103543c4
commit 0c032bc431
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 57 additions and 22 deletions

View File

@ -19,6 +19,7 @@ from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
CODEOWNERS = ["@kuba2k2"] CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["libretiny"] AUTO_LOAD = ["libretiny"]
IS_TARGET_PLATFORM = True
COMPONENT_DATA = LibreTinyComponent( COMPONENT_DATA = LibreTinyComponent(
name=COMPONENT_BK72XX, name=COMPONENT_BK72XX,

View File

@ -64,6 +64,7 @@ from .gpio import esp32_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
IS_TARGET_PLATFORM = True
CONF_RELEASE = "release" CONF_RELEASE = "release"

View File

@ -34,6 +34,7 @@ from .gpio import PinInitialState, add_pin_initial_states_array
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
IS_TARGET_PLATFORM = True
def set_core_data(config): def set_core_data(config):

View File

@ -17,6 +17,7 @@ from .gpio import host_pin_to_code # noqa
CODEOWNERS = ["@esphome/core", "@clydebarrow"] CODEOWNERS = ["@esphome/core", "@clydebarrow"]
AUTO_LOAD = ["network", "preferences"] AUTO_LOAD = ["network", "preferences"]
IS_TARGET_PLATFORM = True
def set_core_data(config): def set_core_data(config):

View File

@ -47,6 +47,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@kuba2k2"] CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
IS_TARGET_PLATFORM = True
def _detect_variant(value): def _detect_variant(value):

View File

@ -27,6 +27,7 @@ from .gpio import rp2040_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
IS_TARGET_PLATFORM = True
def set_core_data(config): def set_core_data(config):

View File

@ -19,6 +19,8 @@ from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS
CODEOWNERS = ["@kuba2k2"] CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["libretiny"] AUTO_LOAD = ["libretiny"]
IS_TARGET_PLATFORM = True
COMPONENT_DATA = LibreTinyComponent( COMPONENT_DATA = LibreTinyComponent(
name=COMPONENT_RTL87XX, name=COMPONENT_RTL87XX,

View File

@ -22,7 +22,6 @@ from esphome.const import (
CONF_PACKAGES, CONF_PACKAGES,
CONF_PLATFORM, CONF_PLATFORM,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
TARGET_PLATFORMS,
) )
from esphome.core import CORE, DocumentRange, EsphomeError from esphome.core import CORE, DocumentRange, EsphomeError
import esphome.core.config as core_config import esphome.core.config as core_config
@ -833,7 +832,7 @@ def validate_config(
result[CONF_ESPHOME] = config[CONF_ESPHOME] result[CONF_ESPHOME] = config[CONF_ESPHOME]
result.add_output_path([CONF_ESPHOME], CONF_ESPHOME) result.add_output_path([CONF_ESPHOME], CONF_ESPHOME)
try: try:
core_config.preload_core_config(config, result) target_platform = core_config.preload_core_config(config, result)
except vol.Invalid as err: except vol.Invalid as err:
result.add_error(err) result.add_error(err)
return result return result
@ -845,9 +844,9 @@ def validate_config(
cv.All(cv.version_number, cv.validate_esphome_version)(min_version) cv.All(cv.version_number, cv.validate_esphome_version)(min_version)
# First run platform validation steps # First run platform validation steps
for key in TARGET_PLATFORMS: result.add_validation_step(
if key in config: LoadValidationStep(target_platform, config[target_platform])
result.add_validation_step(LoadValidationStep(key, config[key])) )
result.run_validation_steps() result.run_validation_steps()
if result.errors: if result.errors:

View File

@ -15,15 +15,6 @@ PLATFORM_LIBRETINY_OLDSTYLE = "libretiny"
PLATFORM_RP2040 = "rp2040" PLATFORM_RP2040 = "rp2040"
PLATFORM_RTL87XX = "rtl87xx" PLATFORM_RTL87XX = "rtl87xx"
TARGET_PLATFORMS = [
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_HOST,
PLATFORM_LIBRETINY_OLDSTYLE,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
]
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}

View File

@ -1,6 +1,7 @@
import logging import logging
import multiprocessing import multiprocessing
import os import os
from pathlib import Path
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
@ -28,7 +29,6 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VERSION, CONF_VERSION,
KEY_CORE, KEY_CORE,
TARGET_PLATFORMS,
__version__ as ESPHOME_VERSION, __version__ as ESPHOME_VERSION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -174,7 +174,31 @@ PRELOAD_CONFIG_SCHEMA = cv.Schema(
) )
def preload_core_config(config, result): def _is_target_platform(name):
from esphome.loader import get_component
try:
if get_component(name, True).is_target_platform:
return True
except KeyError:
pass
return False
def _list_target_platforms():
target_platforms = []
root = Path(__file__).parents[1]
for path in (root / "components").iterdir():
if not path.is_dir():
continue
if not (path / "__init__.py").is_file():
continue
if _is_target_platform(path.name):
target_platforms += [path.name]
return target_platforms
def preload_core_config(config, result) -> str:
with cv.prepend_path(CONF_ESPHOME): with cv.prepend_path(CONF_ESPHOME):
conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME])
@ -187,12 +211,16 @@ def preload_core_config(config, result):
conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name) conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name)
CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH]) CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
target_platforms = [key for key in TARGET_PLATFORMS if key in config] target_platforms = []
for domain, _ in config.items():
if _is_target_platform(domain):
target_platforms += [domain]
if not target_platforms: if not target_platforms:
raise cv.Invalid( raise cv.Invalid(
"Platform missing. You must include one of the available platform keys: " "Platform missing. You must include one of the available platform keys: "
+ ", ".join(TARGET_PLATFORMS), + ", ".join(_list_target_platforms()),
[CONF_ESPHOME], [CONF_ESPHOME],
) )
if len(target_platforms) > 1: if len(target_platforms) > 1:
@ -202,6 +230,7 @@ def preload_core_config(config, result):
) )
config[CONF_ESPHOME] = conf config[CONF_ESPHOME] = conf
return target_platforms[0]
def include_file(path, basename): def include_file(path, basename):

View File

@ -52,6 +52,10 @@ class ComponentManifest:
def is_platform_component(self) -> bool: def is_platform_component(self) -> bool:
return getattr(self.module, "IS_PLATFORM_COMPONENT", False) return getattr(self.module, "IS_PLATFORM_COMPONENT", False)
@property
def is_target_platform(self) -> bool:
return getattr(self.module, "IS_TARGET_PLATFORM", False)
@property @property
def config_schema(self) -> Optional[Any]: def config_schema(self) -> Optional[Any]:
return getattr(self.module, "CONFIG_SCHEMA", None) return getattr(self.module, "CONFIG_SCHEMA", None)
@ -169,13 +173,15 @@ def install_custom_components_meta_finder():
install_meta_finder(custom_components_dir) install_meta_finder(custom_components_dir)
def _lookup_module(domain): def _lookup_module(domain, exception):
if domain in _COMPONENT_CACHE: if domain in _COMPONENT_CACHE:
return _COMPONENT_CACHE[domain] return _COMPONENT_CACHE[domain]
try: try:
module = importlib.import_module(f"esphome.components.{domain}") module = importlib.import_module(f"esphome.components.{domain}")
except ImportError as e: except ImportError as e:
if exception:
raise
if "No module named" in str(e): if "No module named" in str(e):
_LOGGER.info( _LOGGER.info(
"Unable to import component %s: %s", domain, str(e), exc_info=False "Unable to import component %s: %s", domain, str(e), exc_info=False
@ -184,6 +190,8 @@ def _lookup_module(domain):
_LOGGER.error("Unable to import component %s:", domain, exc_info=True) _LOGGER.error("Unable to import component %s:", domain, exc_info=True)
return None return None
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
if exception:
raise
_LOGGER.error("Unable to load component %s:", domain, exc_info=True) _LOGGER.error("Unable to load component %s:", domain, exc_info=True)
return None return None
@ -192,14 +200,14 @@ def _lookup_module(domain):
return manif return manif
def get_component(domain): def get_component(domain, exception=False):
assert "." not in domain assert "." not in domain
return _lookup_module(domain) return _lookup_module(domain, exception)
def get_platform(domain, platform): def get_platform(domain, platform):
full = f"{platform}.{domain}" full = f"{platform}.{domain}"
return _lookup_module(full) return _lookup_module(full, False)
_COMPONENT_CACHE = {} _COMPONENT_CACHE = {}