mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 19:13:18 +08:00
[espidf] Also skip installing gdb and ULP toolchains ESPHome never runs (#17687)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent
559614077d
commit
9dd7dcb06d
+39
-24
@@ -551,30 +551,46 @@ def _patch_tools_json_for_linux_arm64(framework_path: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _patch_tools_json_demote_openocd(framework_path: Path) -> None:
|
||||
"""Demote openocd-esp32 from ``install: always`` to ``install: on_request``.
|
||||
# Tools marked ``install: always`` in tools.json that no ESPHome build ever
|
||||
# runs. openocd-esp32 is a JTAG debug server (its post-install check also
|
||||
# fails outright on systems without libusb-1.0, #17685). The gdb bundles are
|
||||
# debuggers used only by ``idf.py gdb``/``idf.py monitor`` flows ESPHome never
|
||||
# invokes; stack decoding uses addr2line from the compiler toolchains instead.
|
||||
# esp32ulp-elf is the ULP coprocessor toolchain, and ESPHome excludes the IDF
|
||||
# ``ulp`` component from every build. esp-rom-elfs stays required: the cmake
|
||||
# gdbinit generation reads ESP_ROM_ELF_DIR during every configure and warns
|
||||
# when it is missing.
|
||||
_UNUSED_IDF_TOOLS: tuple[str, ...] = (
|
||||
"esp32ulp-elf",
|
||||
"openocd-esp32",
|
||||
"riscv32-esp-elf-gdb",
|
||||
"xtensa-esp-elf-gdb",
|
||||
)
|
||||
|
||||
``idf_tools.py install required`` installs every tool marked ``always`` in
|
||||
tools.json and validates each one after extraction by running its version
|
||||
command. openocd links against libusb-1.0, which minimal systems (bare LXC
|
||||
containers, slim images) often lack, so that one validation aborted the
|
||||
whole framework install and left it permanently retrying (#17685) — even
|
||||
though ESPHome never runs openocd (it is a JTAG debugging tool). Demoting
|
||||
it drops it from the ``required`` set: it is no longer downloaded or
|
||||
validated, and the tool-path export treats a missing ``on_request`` tool
|
||||
as fine. A user who wants it can still name ``openocd-esp32`` explicitly
|
||||
in ESPHOME_IDF_DEFAULT_TOOLS; explicit names bypass install-type
|
||||
filtering.
|
||||
|
||||
Because this runs on every install check, an install stuck in the
|
||||
failing state (which never wrote its stamp file) heals on the next
|
||||
build without a clean.
|
||||
def _patch_tools_json_demote_unused_tools(framework_path: Path) -> None:
|
||||
"""Demote tools ESPHome never runs from ``install: always`` to ``on_request``.
|
||||
|
||||
``idf_tools.py install required`` downloads every tool marked ``always``
|
||||
in tools.json and validates each one after extraction by running its
|
||||
version command. Demoting the tools in ``_UNUSED_IDF_TOOLS`` drops them
|
||||
from the ``required`` set: they are no longer downloaded or validated,
|
||||
and the tool-path export treats a missing ``on_request`` tool as fine.
|
||||
Besides the download and disk savings, this makes the openocd libusb
|
||||
validation failure (#17685) impossible; because this runs on every
|
||||
install check, an install stuck in that failing state (which never wrote
|
||||
its stamp file) heals on the next build without a clean. A user who
|
||||
wants one of these tools can still name it explicitly in
|
||||
ESPHOME_IDF_DEFAULT_TOOLS; explicit names bypass install-type filtering.
|
||||
"""
|
||||
|
||||
def apply_patch(data: dict) -> bool:
|
||||
changed = False
|
||||
for tool in data.get("tools", []):
|
||||
if tool.get("name") == "openocd-esp32" and tool.get("install") == "always":
|
||||
if (
|
||||
tool.get("name") in _UNUSED_IDF_TOOLS
|
||||
and tool.get("install") == "always"
|
||||
):
|
||||
tool["install"] = "on_request"
|
||||
changed = True
|
||||
return changed
|
||||
@@ -582,9 +598,8 @@ def _patch_tools_json_demote_openocd(framework_path: Path) -> None:
|
||||
_patch_tools_json(
|
||||
framework_path,
|
||||
apply_patch,
|
||||
"Patched %s to make openocd-esp32 optional (not needed for "
|
||||
"building, and its install check fails on systems without "
|
||||
"libusb-1.0).",
|
||||
"Patched %s to skip installing tools ESPHome does not use "
|
||||
"(openocd, gdb, ULP toolchain).",
|
||||
)
|
||||
|
||||
|
||||
@@ -779,10 +794,10 @@ def _check_esphome_idf_framework_install(
|
||||
# a pre-patch tools.json get fixed up without forcing a clean.
|
||||
_patch_tools_json_for_linux_arm64(framework_path)
|
||||
|
||||
# Drop openocd-esp32 from the required tool set on every invocation so
|
||||
# an install that previously failed on its libusb check recovers on the
|
||||
# next build.
|
||||
_patch_tools_json_demote_openocd(framework_path)
|
||||
# Drop tools ESPHome never runs from the required tool set on every
|
||||
# invocation, so an install that previously failed on the openocd libusb
|
||||
# check recovers on the next build.
|
||||
_patch_tools_json_demote_unused_tools(framework_path)
|
||||
|
||||
# 3. Check if the framework tools are the same and correctly installed
|
||||
if not install:
|
||||
|
||||
@@ -29,7 +29,7 @@ from esphome.espidf.framework import (
|
||||
_get_python_env_path,
|
||||
_get_python_version,
|
||||
_parse_git_source,
|
||||
_patch_tools_json_demote_openocd,
|
||||
_patch_tools_json_demote_unused_tools,
|
||||
_patch_tools_json_for_linux_arm64,
|
||||
_prefetch_idf_tool_archives,
|
||||
_windows_long_paths_enabled,
|
||||
@@ -352,7 +352,7 @@ def espidf_mocks(setup_core: Path):
|
||||
patch("esphome.espidf.framework._clone_idf_with_submodules") as clone,
|
||||
patch("esphome.espidf.framework._write_idf_version_txt"),
|
||||
patch("esphome.espidf.framework._patch_tools_json_for_linux_arm64"),
|
||||
patch("esphome.espidf.framework._patch_tools_json_demote_openocd"),
|
||||
patch("esphome.espidf.framework._patch_tools_json_demote_unused_tools"),
|
||||
patch("esphome.espidf.framework._prefetch_idf_tool_archives"),
|
||||
patch("esphome.espidf.framework._write_stamp"),
|
||||
patch("esphome.espidf.framework._check_stamp", return_value=True),
|
||||
@@ -952,28 +952,37 @@ def test_get_tool_downloads_inprocess_explicit_tool_specs(
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _patch_tools_json_demote_openocd (openocd-esp32 made optional)
|
||||
# _patch_tools_json_demote_unused_tools (openocd, gdb, ULP toolchain optional)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_demote_openocd_patches_install_type(tmp_path: Path) -> None:
|
||||
def test_demote_unused_tools_patches_install_type(tmp_path: Path) -> None:
|
||||
tools_json = _write_tools_json(
|
||||
tmp_path,
|
||||
{
|
||||
"tools": [
|
||||
{"name": "openocd-esp32", "install": "always"},
|
||||
{"name": "cmake", "install": "always"},
|
||||
{"name": "xtensa-esp-elf-gdb", "install": "always"},
|
||||
{"name": "riscv32-esp-elf-gdb", "install": "always"},
|
||||
{"name": "esp32ulp-elf", "install": "always"},
|
||||
{"name": "xtensa-esp-elf", "install": "always"},
|
||||
{"name": "esp-rom-elfs", "install": "always"},
|
||||
]
|
||||
},
|
||||
)
|
||||
_patch_tools_json_demote_openocd(tmp_path)
|
||||
_patch_tools_json_demote_unused_tools(tmp_path)
|
||||
|
||||
data = json.loads(tools_json.read_text(encoding="utf-8"))
|
||||
openocd = next(t for t in data["tools"] if t["name"] == "openocd-esp32")
|
||||
cmake = next(t for t in data["tools"] if t["name"] == "cmake")
|
||||
assert openocd["install"] == "on_request"
|
||||
# other tools are left untouched
|
||||
assert cmake["install"] == "always"
|
||||
install_types = {t["name"]: t["install"] for t in data["tools"]}
|
||||
assert install_types == {
|
||||
"openocd-esp32": "on_request",
|
||||
"xtensa-esp-elf-gdb": "on_request",
|
||||
"riscv32-esp-elf-gdb": "on_request",
|
||||
"esp32ulp-elf": "on_request",
|
||||
# the compiler toolchain and ROM ELFs stay required
|
||||
"xtensa-esp-elf": "always",
|
||||
"esp-rom-elfs": "always",
|
||||
}
|
||||
|
||||
|
||||
def test_patch_tools_json_unexpected_structure_warns_and_skips(
|
||||
@@ -985,16 +994,24 @@ def test_patch_tools_json_unexpected_structure_warns_and_skips(
|
||||
tools_json = tools_dir / "tools.json"
|
||||
tools_json.write_text('["not", "a", "dict"]', encoding="utf-8")
|
||||
before = tools_json.read_text(encoding="utf-8")
|
||||
_patch_tools_json_demote_openocd(tmp_path) # AttributeError -> skip
|
||||
_patch_tools_json_demote_unused_tools(tmp_path) # AttributeError -> skip
|
||||
assert tools_json.read_text(encoding="utf-8") == before
|
||||
|
||||
|
||||
def test_demote_openocd_already_patched_is_noop(tmp_path: Path) -> None:
|
||||
def test_demote_unused_tools_already_patched_is_noop(tmp_path: Path) -> None:
|
||||
tools_json = _write_tools_json(
|
||||
tmp_path, {"tools": [{"name": "openocd-esp32", "install": "on_request"}]}
|
||||
tmp_path,
|
||||
{
|
||||
"tools": [
|
||||
{"name": "openocd-esp32", "install": "on_request"},
|
||||
{"name": "xtensa-esp-elf-gdb", "install": "on_request"},
|
||||
{"name": "riscv32-esp-elf-gdb", "install": "on_request"},
|
||||
{"name": "esp32ulp-elf", "install": "on_request"},
|
||||
]
|
||||
},
|
||||
)
|
||||
before = tools_json.read_text(encoding="utf-8")
|
||||
_patch_tools_json_demote_openocd(tmp_path)
|
||||
_patch_tools_json_demote_unused_tools(tmp_path)
|
||||
assert tools_json.read_text(encoding="utf-8") == before
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user