diff --git a/esphome/build_helpers/ccache.py b/esphome/build_helpers/ccache.py index 6c9223e4cc..46483f0577 100644 --- a/esphome/build_helpers/ccache.py +++ b/esphome/build_helpers/ccache.py @@ -32,8 +32,8 @@ def _ccache_runs(ccache: str) -> bool: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=15, - # Repo-wide convention (posix_spawn fast path); pinned by - # tests/script/test_helpers.py + # Repo-wide convention (posix_spawn fast path); see the + # close_fds=False call sites across esphome/ and script/helpers.py close_fds=False, ) except (OSError, subprocess.SubprocessError): diff --git a/esphome/espidf/component.py b/esphome/espidf/component.py index fd49c5df05..bd104dfe9a 100644 --- a/esphome/espidf/component.py +++ b/esphome/espidf/component.py @@ -93,7 +93,7 @@ def generate_cmakelists_txt(component: IDFComponent) -> str: ) # PlatformIO shell-lexes each build.flags entry, so one entry can carry a # flag and its argument (e.g. "-include cp_custom_alloc.h"); bare - # -I/-L/-l tokens re-glue to their argument ("-I foo" -> "-Ifoo") so the + # -I/-L/-l/-D tokens re-glue to their argument ("-I foo" -> "-Ifoo") so # prefix classifiers below still route them. build_flags = lex_build_flags(build_flags, f"library {component.name}") diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index a7f7816887..7349c46144 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -23,7 +23,6 @@ import logging import os from pathlib import Path, PurePosixPath import re -import shlex import tempfile from typing import Any from urllib.parse import urlsplit, urlunsplit @@ -562,6 +561,11 @@ def _resolve_registry_version( def split_flag_entry(entry: Any, owner: str) -> list[str]: """``shlex.split`` with a clean error naming the offending flags entry.""" + # Late import: this module loads with the esp32 platform on every + # validate/compile; shlex (and its linecache pull-in) is only needed + # when actually lexing flags + import shlex + try: return shlex.split(entry) except (ValueError, AttributeError, TypeError) as err: diff --git a/tests/unit_tests/test_platformio_extra_script.py b/tests/unit_tests/test_platformio_extra_script.py index e1217a4006..3de9ced3d8 100644 --- a/tests/unit_tests/test_platformio_extra_script.py +++ b/tests/unit_tests/test_platformio_extra_script.py @@ -198,10 +198,14 @@ def test_apply_extra_script_no_script_and_no_flags(tmp_path) -> None: assert "flags" not in c.data["build"] -def test_apply_extra_script_ignores_uncaptured_env_calls(tmp_path) -> None: - """Un-captured env vars and unsupported env methods are silent no-ops.""" +def test_apply_extra_script_ignores_uncaptured_env_calls(tmp_path, caplog) -> None: + """Un-captured env vars and unsupported env methods are skipped but + diagnosable from the build log.""" + import logging + from esphome.platformio.extra_script import apply_extra_script + caplog.set_level(logging.DEBUG) script = tmp_path / "extra.py" script.write_text( "env.Replace(CC='clang')\nenv.Append(UNCAPTURED=['x'], LIBS='single')\n" @@ -211,6 +215,8 @@ def test_apply_extra_script_ignores_uncaptured_env_calls(tmp_path) -> None: c.data = {"build": {"extraScript": "extra.py"}} apply_extra_script(c, board_mcu=lambda: "esp8266", pio_platform="espressif8266") assert c.data["build"]["flags"] == ["-lsingle"] + assert "env.Append(UNCAPTURED=...) is not captured" in caplog.text + assert "env.Replace(...) is a no-op here" in caplog.text def test_apply_extra_script_swallows_script_errors(tmp_path, caplog) -> None: diff --git a/tests/unit_tests/test_platformio_toolchain.py b/tests/unit_tests/test_platformio_toolchain.py index 416deceadb..77d9ed436e 100644 --- a/tests/unit_tests/test_platformio_toolchain.py +++ b/tests/unit_tests/test_platformio_toolchain.py @@ -1975,3 +1975,12 @@ def test_run_platformio_cli_invokes_heal( with patch.object(toolchain, "heal_platformio_python_env") as mock_heal: toolchain.run_platformio_cli("test") mock_heal.assert_called_once() + + +def test_ccache_probe_spawns_with_close_fds_false() -> None: + """The probe follows the repo-wide posix_spawn convention.""" + from esphome.build_helpers import ccache + + with patch("subprocess.run") as mock_run: + assert ccache._ccache_runs("/usr/bin/ccache") is True + assert mock_run.call_args.kwargs["close_fds"] is False