mirror of
https://github.com/esphome/esphome.git
synced 2026-10-28 00:42:11 +00:00
[logger] Defer platform imports out of module scope (#18336)
This commit is contained in:
1 parent
09f7b71efd
commit
db76f0b1cd
2 files changed
+141
-59
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
@@ -5,37 +6,6 @@ from typing import Any
|
||||
from esphome import automation
|
||||
from esphome.automation import LambdaAction, StatelessLambdaAction
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32H4,
|
||||
VARIANT_ESP32H21,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32S31,
|
||||
add_idf_sdkconfig_option,
|
||||
get_esp32_variant,
|
||||
idf_version,
|
||||
require_usb_serial_jtag_secondary,
|
||||
require_vfs_termios,
|
||||
)
|
||||
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_BK72XX,
|
||||
COMPONENT_LN882X,
|
||||
COMPONENT_RTL87XX,
|
||||
)
|
||||
from esphome.components.zephyr import (
|
||||
zephyr_add_overlay,
|
||||
zephyr_add_prj_conf,
|
||||
zephyr_claim_cdc_acm,
|
||||
)
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -117,30 +87,8 @@ CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
|
||||
CONF_WAIT_FOR_CDC = "wait_for_cdc"
|
||||
CONF_EARLY_MESSAGE = "early_message"
|
||||
|
||||
UART_SELECTION_ESP32 = {
|
||||
VARIANT_ESP32: [UART0, UART1, UART2],
|
||||
VARIANT_ESP32C2: [UART0, UART1],
|
||||
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H21: [UART0, UART1, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
|
||||
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32S31: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
}
|
||||
|
||||
UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
|
||||
|
||||
UART_SELECTION_LIBRETINY = {
|
||||
COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
|
||||
COMPONENT_LN882X: [DEFAULT, UART0, UART1, UART2],
|
||||
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
|
||||
}
|
||||
|
||||
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
|
||||
|
||||
UART_SELECTION_NRF52 = [USB_CDC, UART0]
|
||||
@@ -173,22 +121,99 @@ HARDWARE_UART_TO_SERIAL = {
|
||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _uart_selection_esp32() -> dict[str, list[str]]:
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32H4,
|
||||
VARIANT_ESP32H21,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32S31,
|
||||
)
|
||||
|
||||
return {
|
||||
VARIANT_ESP32: [UART0, UART1, UART2],
|
||||
VARIANT_ESP32C2: [UART0, UART1],
|
||||
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H21: [UART0, UART1, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
|
||||
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32S31: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
}
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _uart_selection_libretiny() -> dict[str, list[str]]:
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_BK72XX,
|
||||
COMPONENT_LN882X,
|
||||
COMPONENT_RTL87XX,
|
||||
)
|
||||
|
||||
return {
|
||||
COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
|
||||
COMPONENT_LN882X: [DEFAULT, UART0, UART1, UART2],
|
||||
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
|
||||
}
|
||||
|
||||
|
||||
# These tables are introspected by external tooling (esphome/device-builder);
|
||||
# built on access so the platform packages stay out of module import.
|
||||
_LAZY_UART_SELECTION_TABLES = {
|
||||
"UART_SELECTION_ESP32": _uart_selection_esp32,
|
||||
"UART_SELECTION_LIBRETINY": _uart_selection_libretiny,
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> dict[str, list[str]]:
|
||||
if (table := _LAZY_UART_SELECTION_TABLES.get(name)) is not None:
|
||||
return table()
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return [*globals(), *_LAZY_UART_SELECTION_TABLES]
|
||||
|
||||
|
||||
def uart_selection(value: Any) -> str:
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
|
||||
uart_selection_esp32 = _uart_selection_esp32()
|
||||
variant = get_esp32_variant()
|
||||
if variant in UART_SELECTION_ESP32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||
if variant in uart_selection_esp32:
|
||||
return cv.one_of(*uart_selection_esp32[variant], upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
if CORE.is_rp2:
|
||||
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
|
||||
if CORE.is_libretiny:
|
||||
from esphome.components.libretiny import (
|
||||
get_libretiny_component,
|
||||
get_libretiny_family,
|
||||
)
|
||||
|
||||
uart_selection_libretiny = _uart_selection_libretiny()
|
||||
family = get_libretiny_family()
|
||||
if family in UART_SELECTION_LIBRETINY:
|
||||
return cv.one_of(*UART_SELECTION_LIBRETINY[family], upper=True)(value)
|
||||
if family in uart_selection_libretiny:
|
||||
return cv.one_of(*uart_selection_libretiny[family], upper=True)(value)
|
||||
component = get_libretiny_component()
|
||||
if component in UART_SELECTION_LIBRETINY:
|
||||
return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value)
|
||||
if component in uart_selection_libretiny:
|
||||
return cv.one_of(*uart_selection_libretiny[component], upper=True)(value)
|
||||
if CORE.is_host:
|
||||
raise cv.Invalid("Uart selection not valid for host platform")
|
||||
if CORE.is_nrf52:
|
||||
@@ -414,6 +439,8 @@ def _final_validate(config: ConfigType) -> None:
|
||||
and config[CONF_BAUD_RATE] != 0
|
||||
and config.get(CONF_HARDWARE_UART) == USB_CDC
|
||||
):
|
||||
from esphome.components.zephyr import zephyr_claim_cdc_acm
|
||||
|
||||
zephyr_claim_cdc_acm(0, "logger", write_only=True)
|
||||
|
||||
|
||||
@@ -495,6 +522,8 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
level = config[CONF_LEVEL]
|
||||
baud_rate: int = config[CONF_BAUD_RATE]
|
||||
if CORE.using_zephyr:
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
|
||||
task_log_buffer_size = config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
|
||||
if task_log_buffer_size > 0:
|
||||
zephyr_add_prj_conf("MPSC_PBUF", True)
|
||||
@@ -559,6 +588,14 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
if CORE.is_esp32 and is_at_least_very_verbose:
|
||||
cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER")
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32S2,
|
||||
add_idf_sdkconfig_option,
|
||||
get_esp32_variant,
|
||||
idf_version,
|
||||
)
|
||||
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
@@ -591,6 +628,11 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
and config[CONF_HARDWARE_UART] != USB_SERIAL_JTAG
|
||||
and has_serial_logging
|
||||
):
|
||||
from esphome.components.esp32 import (
|
||||
require_usb_serial_jtag_secondary,
|
||||
require_vfs_termios,
|
||||
)
|
||||
|
||||
require_usb_serial_jtag_secondary()
|
||||
require_vfs_termios()
|
||||
except cv.Invalid:
|
||||
@@ -607,6 +649,8 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
cg.add_define("USE_LOGGER_EARLY_MESSAGE")
|
||||
|
||||
if CORE.is_nrf52:
|
||||
from esphome.components.zephyr import zephyr_add_overlay, zephyr_add_prj_conf
|
||||
|
||||
# esphome implement own fatal error handler which save PC/LR before reset
|
||||
zephyr_add_prj_conf("RESET_ON_FATAL_ERROR", False)
|
||||
zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for the logger component's lazy platform tables."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from esphome.components import logger
|
||||
|
||||
|
||||
def test_uart_selection_tables_exposed_for_external_tooling() -> None:
|
||||
"""device-builder introspects these names off the module; keep them
|
||||
accessible (and discoverable) and stable between accesses."""
|
||||
assert "UART_SELECTION_ESP32" in dir(logger)
|
||||
assert "UART_SELECTION_LIBRETINY" in dir(logger)
|
||||
assert logger.UART_SELECTION_ESP32["ESP32C3"] == [
|
||||
"UART0",
|
||||
"UART1",
|
||||
"USB_CDC",
|
||||
"USB_SERIAL_JTAG",
|
||||
]
|
||||
assert "bk72xx" in logger.UART_SELECTION_LIBRETINY
|
||||
# Repeated access returns the same cached object
|
||||
assert logger.UART_SELECTION_ESP32 is logger.UART_SELECTION_ESP32
|
||||
|
||||
|
||||
def test_dir_does_not_import_platform_packages() -> None:
|
||||
"""Listing the names must not load the platform packages; only reading a
|
||||
table may. Checked in a fresh interpreter so nothing loaded by other tests
|
||||
hides a leak, and nothing here changes what later tests see."""
|
||||
check = (
|
||||
"import sys; from esphome.components import logger; dir(logger); "
|
||||
"print(','.join(m for m in ('esphome.components.esp32', "
|
||||
"'esphome.components.libretiny', 'esphome.components.zephyr') "
|
||||
"if m in sys.modules))"
|
||||
)
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", check], capture_output=True, text=True, check=True
|
||||
)
|
||||
assert result.stdout.strip() == ""
|
||||
Reference in new issue
Block a user