diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 6e3087ba9f0..6806eab0cf3 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -335,6 +335,16 @@ def final_validation(config_list): widget_type.final_validate(name, conf, widget_conf, path[1:]) +def lv_conf_path_flag(lv_conf_h_file: Path) -> str: + """The ``-DLV_CONF_PATH`` build flag, decoded identically by the shell + lexers (PlatformIO, host, ESP8266) and the quoted CMake arguments + (ESP-IDF, Zephyr) the build flags pass through.""" + # forward slashes for Windows, then escape what shlex or a quoted CMake + # argument would not pass through literally + path = re.sub(r"[^\w/.:+=,-]", r"\\\g<0>", Path(lv_conf_h_file).as_posix()) + return f'-DLV_CONF_PATH=\\"{path}\\"' + + async def to_code(configs): config_0 = configs[0] # Global configuration @@ -563,9 +573,7 @@ async def to_code(configs): if write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()): clean_build(clear_pio_cache=False) cg.add_build_flag("-DLV_CONF_H=1") - # handle windows paths in a way that doesn't break the generated C++ - lv_conf_h_path = Path(lv_conf_h_file).as_posix() - cg.add_build_flag(f'-DLV_CONF_PATH=\\"{lv_conf_h_path}\\"') + cg.add_build_flag(lv_conf_path_flag(lv_conf_h_file)) cg.add_build_flag("-DLV_KCONFIG_IGNORE") for prop in df.get_remapped_uses(): diff --git a/tests/component_tests/lvgl/test_lv_conf_path_flag.py b/tests/component_tests/lvgl/test_lv_conf_path_flag.py new file mode 100644 index 00000000000..e13a0bcc376 --- /dev/null +++ b/tests/component_tests/lvgl/test_lv_conf_path_flag.py @@ -0,0 +1,39 @@ +"""The ``-DLV_CONF_PATH`` build flag carries a path. PlatformIO, the host +build and the native ESP8266 build shell-lex build flags, while the ESP-IDF +and Zephyr builds write each flag into a quoted CMake argument; both must +decode it to one ``-DLV_CONF_PATH="..."`` argument, including when the +project path contains a space.""" + +from __future__ import annotations + +from pathlib import Path +import re +import shlex + +import pytest + +from esphome.components.lvgl import lv_conf_path_flag + + +def _cmake_quoted_argument(flag: str) -> str: + """Decode ``flag`` as CMake decodes a quoted argument: a backslash escapes the next character.""" + return re.sub(r"\\(.)", r"\1", flag) + + +@pytest.mark.parametrize( + "build_path", + [ + "/home/user/.esphome/build/node", + "/Users/me/with space/.esphome/build/node", + "/srv/odd 'quote' $(and)/build", + "C:/Users/Kris Maddens/esphome/.esphome/build/node", + ], + ids=["plain", "space", "shell-specials", "windows"], +) +def test_flag_is_one_argument_everywhere(build_path: str) -> None: + lv_conf_h_file = Path(build_path) / "src" / "lv_conf.h" + flag = lv_conf_path_flag(lv_conf_h_file) + expected = f'-DLV_CONF_PATH="{lv_conf_h_file.as_posix()}"' + + assert shlex.split(flag) == [expected] + assert _cmake_quoted_argument(flag) == expected