From 7f2515edf1db6b86e0fcb4ddde0e6a822964d95b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 05:03:49 -0500 Subject: [PATCH] Share the PlatformIO flag-lexing helper and flatten the build_flags classifier --- esphome/arduino8266/component.py | 21 +++---------------- esphome/build_gen/arduino8266.py | 36 ++++++++++++++++---------------- esphome/espidf/component.py | 24 +++++++-------------- esphome/platformio/library.py | 17 ++++++++++++++- 4 files changed, 45 insertions(+), 53 deletions(-) diff --git a/esphome/arduino8266/component.py b/esphome/arduino8266/component.py index be9c1c7ee9..3bd96be0ef 100644 --- a/esphome/arduino8266/component.py +++ b/esphome/arduino8266/component.py @@ -13,7 +13,6 @@ include path. from __future__ import annotations -from collections.abc import Iterable from dataclasses import dataclass, field import logging from pathlib import Path @@ -33,6 +32,7 @@ from esphome.platformio.library import ( convert_libraries, ensure_list, is_lib_ignored, + join_flag_args, lib_ignore_set, normalize_dependencies, parse_library_properties, @@ -59,21 +59,6 @@ class ArduinoLibrary: link_flags: list[str] = field(default_factory=list) -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] = [] - it = iter(tokens) - for tok in it: - if tok in ("-I", "-L", "-l"): - arg = next(it, None) - if arg is None: - _LOGGER.warning("Ignoring trailing '%s' in %s build flags", tok, owner) - break - tok += arg - out.append(tok) - return out - - def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary: """Resolve one library's sources, include dirs, and flags (PIO semantics).""" build = data.get("build", {}) @@ -90,7 +75,7 @@ def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary: src_filter = ensure_list(build.get("srcFilter", DEFAULT_BUILD_SRC_FILTER)) # PlatformIO shell-lexes each build.flags entry - raw_flags = join_flag_args( + flag_tokens = join_flag_args( ( token for entry in ensure_list(build.get("flags", [])) @@ -101,7 +86,7 @@ def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary: lib = ArduinoLibrary(name=name) include_flags: list[str] = [] - for tok in raw_flags: + for tok in flag_tokens: if tok.startswith("-I"): include_flags.append(tok[2:]) elif tok.startswith("-L"): diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index ba5f4ee1ea..3eb2d32b84 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -22,7 +22,6 @@ import shlex import subprocess import sys -from esphome.arduino8266.component import join_flag_args from esphome.components.esp8266 import build_surgery from esphome.components.esp8266.boards import ( BOARDS, @@ -40,6 +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 # Compile rule per source suffix; keys must cover SRC_FILE_EXTENSIONS so any # source a library manifest selects has a rule (pinned by a drift test). @@ -308,23 +308,23 @@ def _project_flags() -> tuple[list[str], list[str], list[Path], list[str]]: lib_dirs: list[Path] = [] libs: list[str] = [] for flag in flags: - if flag.startswith("-Wl,"): - link_flags.append(flag) - elif flag.startswith(("-L", "-l")): - # Shell-lex only linker entries so forms like "-L /opt/blobs" - # work as they do under PlatformIO. Other entries pass verbatim: - # lexing them would strip the quotes in defines like -DBOARD="...". - for tok in join_flag_args(shlex.split(flag), "esphome"): - if tok.startswith("-L") and len(tok) > 2: - lib_dirs.append(Path(tok[2:])) - elif tok.startswith("-l") and len(tok) > 2: - libs.append(tok[2:]) - elif tok.startswith("-Wl,"): - link_flags.append(tok) - else: - compile_flags.append(tok) - else: - compile_flags.append(flag) + # Shell-lex only linker entries so forms like "-L /opt/blobs" work as + # 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") + if flag.startswith(("-L", "-l")) + else [flag] + ) + for tok in tokens: + if tok.startswith("-Wl,"): + link_flags.append(tok) + elif tok.startswith("-L"): + lib_dirs.append(Path(tok[2:])) + elif tok.startswith("-l"): + libs.append(tok[2:]) + else: + compile_flags.append(tok) return compile_flags, link_flags, lib_dirs, libs diff --git a/esphome/espidf/component.py b/esphome/espidf/component.py index b9d1f21753..1c951ae274 100644 --- a/esphome/espidf/component.py +++ b/esphome/espidf/component.py @@ -27,6 +27,7 @@ from esphome.platformio.library import ( collect_filtered_files, convert_libraries, ensure_list, + join_flag_args, split_list_by_condition, ) @@ -102,22 +103,13 @@ def generate_cmakelists_txt(component: IDFComponent) -> str: component.data.get("build", {}).get("flags", DEFAULT_BUILD_FLAGS) ) # PlatformIO shell-lexes each build.flags entry, so one entry can carry a - # flag and its argument (e.g. "-include cp_custom_alloc.h"). Split the - # same way; emitting such an entry as a single quoted compile option - # hands the compiler one argv with an embedded space. - build_flags = [token for entry in build_flags for token in shlex.split(entry)] - # Re-glue bare -I/-L/-l tokens to their argument ("-I foo" -> "-Ifoo") so - # the prefix classifiers below still route them to INCLUDE_DIRS and the - # link handling. - tokens, build_flags = build_flags, [] - i = 0 - while i < len(tokens): - if tokens[i] in ("-I", "-L", "-l") and i + 1 < len(tokens): - build_flags.append(tokens[i] + tokens[i + 1]) - i += 2 - else: - build_flags.append(tokens[i]) - i += 1 + # 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 + # prefix classifiers below still route them. + build_flags = join_flag_args( + (token for entry in build_flags for token in shlex.split(entry)), + f"library {component.name}", + ) # List all sources files build_src_files = collect_filtered_files( diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index c8896d9202..5c936c280f 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -13,7 +13,7 @@ regardless of which toolchain consumes the result. """ from collections import deque -from collections.abc import Callable +from collections.abc import Callable, Iterable from dataclasses import dataclass, field import glob import hashlib @@ -553,6 +553,21 @@ def _resolve_registry_version( return owner, name, best["name"], pkgfile["download_url"] +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] = [] + it = iter(tokens) + for tok in it: + if tok in ("-I", "-L", "-l"): + arg = next(it, None) + if arg is None: + _LOGGER.warning("Ignoring trailing '%s' in %s build flags", tok, owner) + break + tok += arg + out.append(tok) + return out + + def normalize_dependencies(dependencies: Any) -> list[dict]: """Normalize a library manifest's ``dependencies`` to a list of dicts.