mirror of
https://github.com/esphome/esphome.git
synced 2026-10-05 18:41:32 +00:00
[espidf] Resolve the ESP-IDF tool paths once per build (#19996)
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from ctypes.util import find_library
|
||||
from dataclasses import dataclass, field
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -18,7 +19,7 @@ from esphome.build_helpers.ccache import (
|
||||
)
|
||||
from esphome.build_helpers.pch import ccache_pch_env
|
||||
from esphome.build_helpers.tools_cache import IDF_TOOLS_CACHE, tools_cache_path
|
||||
from esphome.core import Version
|
||||
from esphome.core import CORE, Version
|
||||
from esphome.framework_helpers import (
|
||||
PathType,
|
||||
create_venv,
|
||||
@@ -42,6 +43,21 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_SCRIPTS_DIR = Path(__file__).parent
|
||||
|
||||
DOMAIN = "espidf_framework"
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FrameworkCache:
|
||||
tool_paths: dict[Path, tuple[list[str], dict[str, str]]] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
|
||||
def _cache() -> _FrameworkCache:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = _FrameworkCache()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
ESPHOME_STAMP_FILE = ".esphome.stamp.json"
|
||||
|
||||
@@ -362,7 +378,17 @@ def _get_idf_tool_paths(
|
||||
|
||||
Raises:
|
||||
RuntimeError: If ESP-IDF tool paths cannot be determined
|
||||
|
||||
The install check and the build environment both resolve the same
|
||||
framework, so the result is cached per run and the helper script runs
|
||||
once per build instead of once per caller. The script also reads
|
||||
``IDF_TOOLS_PATH``; every caller sets it from ``get_idf_tools_path()``,
|
||||
so the key leaves it out.
|
||||
"""
|
||||
cache = _cache().tool_paths
|
||||
key = Path(idf_framework_root)
|
||||
if (cached := cache.get(key)) is not None:
|
||||
return cached
|
||||
|
||||
success, stdout, stderr = _run_idf_tools_script(
|
||||
idf_framework_root, "get_idf_tool_paths.py", "ESP-IDF tool paths", env=env
|
||||
@@ -373,11 +399,13 @@ def _get_idf_tool_paths(
|
||||
# Extract json values
|
||||
try:
|
||||
data = json.loads(stdout)
|
||||
return data["paths_to_export"], data["export_vars"]
|
||||
result = (data["paths_to_export"], data["export_vars"])
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"Can't extract ESP-IDF tool paths of {idf_framework_root}"
|
||||
) from e
|
||||
cache[key] = result
|
||||
return result
|
||||
|
||||
|
||||
def _get_python_version(
|
||||
@@ -927,6 +955,7 @@ def _check_esphome_idf_framework_install(
|
||||
# Validate via the managed tool-path resolution, not ``idf_tools.py check``:
|
||||
# ``check`` probes tools on the system PATH and aborts if any fail to run (e.g. a
|
||||
# broken Homebrew openocd), which forced a toolchain reinstall on every build.
|
||||
# The resolved paths stay cached for get_framework_env.
|
||||
try:
|
||||
_get_idf_tool_paths(framework_path, env)
|
||||
install = False
|
||||
|
||||
@@ -1511,6 +1511,30 @@ def test_get_idf_tool_paths_raises_on_failure(tmp_path: Path) -> None:
|
||||
_get_idf_tool_paths(tmp_path)
|
||||
|
||||
|
||||
def test_get_idf_tool_paths_runs_the_script_once_per_build(tmp_path: Path) -> None:
|
||||
payload = json.dumps({"paths_to_export": ["/a"], "export_vars": {"X": "1"}})
|
||||
env = {"IDF_TOOLS_PATH": str(tmp_path / "tools")}
|
||||
with patch(
|
||||
"esphome.espidf.framework.run_command", return_value=(True, payload, "")
|
||||
) as run:
|
||||
first = _get_idf_tool_paths(tmp_path, env)
|
||||
second = _get_idf_tool_paths(tmp_path, env)
|
||||
assert run.call_count == 1
|
||||
assert first == second == (["/a"], {"X": "1"})
|
||||
|
||||
|
||||
def test_get_idf_tool_paths_does_not_cache_a_failure(tmp_path: Path) -> None:
|
||||
payload = json.dumps({"paths_to_export": ["/a"], "export_vars": {}})
|
||||
with patch(
|
||||
"esphome.espidf.framework.run_command",
|
||||
side_effect=[(False, "", "err"), (True, payload, "")],
|
||||
) as run:
|
||||
with pytest.raises(RuntimeError, match="Can't get ESP-IDF tool paths"):
|
||||
_get_idf_tool_paths(tmp_path)
|
||||
assert _get_idf_tool_paths(tmp_path) == (["/a"], {})
|
||||
assert run.call_count == 2
|
||||
|
||||
|
||||
def test_get_python_version_parses_stdout(tmp_path: Path) -> None:
|
||||
with patch(
|
||||
"esphome.espidf.framework.run_command", return_value=(True, "3.11.0\n", "")
|
||||
|
||||
Reference in New Issue
Block a user