Surface malformed build flag entries as clean errors

This commit is contained in:
J. Nick Koston
2026-08-20 07:47:54 -05:00
parent 2d93300903
commit 0e561d8c1f
5 changed files with 30 additions and 11 deletions
+2 -2
View File
@@ -16,7 +16,6 @@ from __future__ import annotations
from dataclasses import dataclass, field
import logging
from pathlib import Path
import shlex
from esphome.core import CORE, Library
from esphome.espidf.extra_script import apply_extra_script
@@ -36,6 +35,7 @@ from esphome.platformio.library import (
lib_ignore_set,
normalize_dependencies,
parse_library_properties,
split_flag_entry,
)
_LOGGER = logging.getLogger(__name__)
@@ -79,7 +79,7 @@ def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary:
(
token
for entry in ensure_list(build.get("flags", []))
for token in shlex.split(entry)
for token in split_flag_entry(entry, f"library {name}")
),
f"library {name}",
)
+3 -4
View File
@@ -19,7 +19,6 @@ import logging
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
@@ -40,7 +39,7 @@ from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE, EsphomeError
from esphome.framework_helpers import get_project_cxx_compile_flags
from esphome.helpers import mkdir_p, write_file_if_changed
from esphome.platformio.library import join_flag_args
from esphome.platformio.library import join_flag_args, split_flag_entry
_LOGGER = logging.getLogger(__name__)
@@ -179,7 +178,7 @@ def _flag_defines() -> dict[str, str]:
# Shell-lex multi-token entries the way PlatformIO does, so a knob
# in "-DKNOB -DOTHER" is still detected; single tokens pass verbatim
# to keep any quoting in their bodies intact.
for tok in shlex.split(flag) if " " in flag else (flag,):
for tok in split_flag_entry(flag, "esphome") if " " in flag else (flag,):
if tok.startswith("-D"):
body = tok[2:]
defines[body.split("=", 1)[0]] = body
@@ -319,7 +318,7 @@ def _project_flags() -> tuple[list[str], list[str], list[Path], list[str]]:
# they do under PlatformIO. Other entries pass verbatim: lexing them
# would strip the quotes in defines like -DBOARD="...".
tokens = (
join_flag_args(shlex.split(flag), "esphome")
join_flag_args(split_flag_entry(flag, "esphome"), "esphome")
if flag.startswith(("-L", "-l"))
else [flag]
)
+6 -5
View File
@@ -28,6 +28,7 @@ from esphome.platformio.library import (
convert_libraries,
ensure_list,
join_flag_args,
split_flag_entry,
split_list_by_condition,
)
@@ -62,10 +63,6 @@ def generate_cmakelists_txt(component: IDFComponent) -> str:
Returns:
str: The complete CMakeLists.txt content as a string
"""
# Late import: this module loads with the esp32 platform on every
# validate/compile, but shlex is only needed when generating component
# CMakeLists.
import shlex
def escape_entry(p: PathType) -> str:
# In CMakeLists.txt, backslashes need to be escaped
@@ -107,7 +104,11 @@ def generate_cmakelists_txt(component: IDFComponent) -> str:
# -I/-L/-l tokens re-glue to their argument ("-I foo" -> "-Ifoo") so the
# prefix classifiers below still route them.
build_flags = join_flag_args(
(token for entry in build_flags for token in shlex.split(entry)),
(
token
for entry in build_flags
for token in split_flag_entry(entry, f"library {component.name}")
),
f"library {component.name}",
)
+10
View File
@@ -553,6 +553,16 @@ def _resolve_registry_version(
return owner, name, best["name"], pkgfile["download_url"]
def split_flag_entry(entry: str, owner: str) -> list[str]:
"""``shlex.split`` with a clean error naming the offending flags entry."""
import shlex
try:
return shlex.split(entry)
except ValueError as err:
raise EsphomeError(f"Malformed build flag {entry!r} in {owner}: {err}") from err
def join_flag_args(tokens: Iterable[str], owner: str) -> list[str]:
"""Join a bare ``-I``/``-L``/``-l`` with its following token (PIO lexing)."""
out: list[str] = []
@@ -531,3 +531,12 @@ def test_convert_libraries_skips_incompatible_dependency(tmp_path, monkeypatch):
top = convert_libraries([Library("esphome/A", "1.0.0", None)], _backend())
assert top[0].dependencies == []
def test_split_flag_entry_unbalanced_quote_is_clean() -> None:
"""A malformed flags entry raises EsphomeError, not a raw ValueError."""
from esphome.platformio.library import split_flag_entry
assert split_flag_entry('-DX="a b"', "library x") == ["-DX=a b"]
with pytest.raises(EsphomeError, match=r"Malformed build flag.*library x"):
split_flag_entry('-DX="unclosed', "library x")