diff --git a/esphome/arduino8266/component.py b/esphome/arduino8266/component.py index 73121bee87..42f2b72c77 100644 --- a/esphome/arduino8266/component.py +++ b/esphome/arduino8266/component.py @@ -31,6 +31,8 @@ from esphome.platformio.library import ( collect_filtered_files, convert_libraries, ensure_list, + is_lib_ignored, + lib_ignore_set, normalize_dependencies, parse_library_properties, ) @@ -134,12 +136,9 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: external: list[Library] = [] # PlatformIO's lib_ignore covers framework-bundled libraries too; the # shared converter only filters the registry/git ones. - lib_ignore = { - ignore.split("/")[-1].lower() - for ignore in CORE.platformio_options.get("lib_ignore", []) - } + lib_ignore = lib_ignore_set() for library in CORE.platformio_libraries.values(): - if library.name and library.name.split("/")[-1].lower() in lib_ignore: + if is_lib_ignored(library.name, lib_ignore): continue # A version pin means a registry package ("pngle@1.1.0"), never a # framework-bundled library. @@ -174,7 +173,7 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: or not (framework_path / "libraries" / name).is_dir() ): continue - if name.lower() in lib_ignore: + if is_lib_ignored(name, lib_ignore): continue try: check_library_data(dep, ESP8266_PLATFORM, "arduino") diff --git a/esphome/arduino8266/framework.py b/esphome/arduino8266/framework.py index e75ef892f3..73197ae72c 100644 --- a/esphome/arduino8266/framework.py +++ b/esphome/arduino8266/framework.py @@ -121,9 +121,7 @@ def _pio_system() -> str: return "linux_x86_64" -def _registry_download( - package: str, version: str -) -> tuple[str, str | None, int | None]: +def _registry_download(package: str, version: str) -> tuple[str, str, int | None]: """Resolve a package's download URL, sha256, and size via the PIO registry.""" import requests diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 65e184e127..9274198f42 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -493,10 +493,9 @@ def write_project(paths: dict[str, Path]) -> bool: # as under PlatformIO (a silently ignored ``build_unflags: -Os`` would # diverge between the toolchains). unflags = set(CORE.build_unflags) - if unflags: - cflags = [f for f in cflags if f not in unflags] - cxxflags = [f for f in cxxflags if f not in unflags] - asflags = [f for f in asflags if f not in unflags] + cflags = [f for f in cflags if f not in unflags] + cxxflags = [f for f in cxxflags if f not in unflags] + asflags = [f for f in asflags if f not in unflags] link_flags = [f for f in _LINKFLAGS if f not in unflags] if esp8266_data.get(KEY_SCANF_FLOAT): @@ -522,6 +521,10 @@ def write_project(paths: dict[str, Path]) -> bool: build_tool = Path(__file__).parent.parent / "arduino8266" / "build_tool.py" ccache = ccache_path() + # $in/$out stay unquoted in the rule commands: ninja shell-escapes its + # built-in path variables itself when expanding a command (POSIX and + # Windows), so adding quotes would wrap ninja's own quoting and break + # space-containing paths. Only literal paths need _q(). lines = [ "# Auto-generated by ESPHome", "ninja_required_version = 1.5", @@ -532,35 +535,35 @@ def write_project(paths: dict[str, Path]) -> bool: f"ccache = {_q(ccache) if ccache else ''}", "", "rule cc", - ' command = $ccache $cc -MMD -MF "$out.d" $cflags $flags -c "$in" -o "$out"', + " command = $ccache $cc -MMD -MF $out.d $cflags $flags -c $in -o $out", " depfile = $out.d", " deps = gcc", " description = CC $out", "rule cxx", - ' command = $ccache $cxx -MMD -MF "$out.d" $cxxflags $flags -c "$in" -o "$out"', + " command = $ccache $cxx -MMD -MF $out.d $cxxflags $flags -c $in -o $out", " depfile = $out.d", " deps = gcc", " description = CXX $out", "rule asm", - ' command = $ccache $cc -MMD -MF "$out.d" -x assembler-with-cpp $asflags $flags -c "$in" -o "$out"', + " command = $ccache $cc -MMD -MF $out.d -x assembler-with-cpp $asflags $flags -c $in -o $out", " depfile = $out.d", " deps = gcc", " description = AS $out", "rule ar", - f' command = $python $buildtool ar {_q(toolchain_bin / "xtensa-lx106-elf-ar")} "$out" "$out.rsp"', + f" command = $python $buildtool ar {_q(toolchain_bin / 'xtensa-lx106-elf-ar')} $out $out.rsp", " rspfile = $out.rsp", " rspfile_content = $in_newline", " description = AR $out", "rule link", - ' command = $cxx -o "$out" $linkflags @"$out.rsp" $libdirflags -Wl,--start-group $archives $libflags -Wl,--end-group', + " command = $cxx -o $out $linkflags @$out.rsp $libdirflags -Wl,--start-group $archives $libflags -Wl,--end-group", " rspfile = $out.rsp", " rspfile_content = $in_newline", " description = LINK $out", "rule elf2bin", - f' command = $python {_q(framework / "tools" / "elf2bin.py")} --eboot {_q(framework / "bootloaders" / "eboot" / "eboot.elf")} --app "$in" --flash_mode {esp8266_data[KEY_FLASH_MODE]} --flash_freq 40 --flash_size {_flash_size_str(flash_ld_name)} --path {_q(toolchain_bin)} --out "$out"', + f" command = $python {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {esp8266_data[KEY_FLASH_MODE]} --flash_freq 40 --flash_size {_flash_size_str(flash_ld_name)} --path {_q(toolchain_bin)} --out $out", " description = BIN $out", "rule copy", - ' command = $python $buildtool copy "$in" "$out"', + " command = $python $buildtool copy $in $out", " description = COPY $out", "", f"cflags = {' '.join(cflags)}", diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index 22a86af34f..c8896d9202 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -688,6 +688,24 @@ def _node_key( return name, "registry", (owner, pkgname) +def lib_ignore_set() -> set[str]: + """The ``lib_ignore`` names from ``esphome->platformio_options``, + normalized to lowercase short names (the part after the ``/``).""" + return { + name.split("/")[-1].lower() + for name in CORE.platformio_options.get("lib_ignore", []) + } + + +def is_lib_ignored(name: str | None, lib_ignore: set[str]) -> bool: + """Whether ``name`` matches the normalized ``lib_ignore`` set.""" + return ( + bool(lib_ignore) + and name is not None + and (name.split("/")[-1].lower() in lib_ignore) + ) + + def convert_libraries( libraries: list[Library], backend: LibraryBackend ) -> list[ConvertedLibrary]: @@ -713,10 +731,7 @@ def convert_libraries( """ nodes: dict[str, _LibNode] = {} - lib_ignore = { - name.split("/")[-1].lower() - for name in CORE.platformio_options.get("lib_ignore", []) - } + lib_ignore = lib_ignore_set() # The generated build files inside the shared cache bake in the dependency # wiring, which lib_ignore changes; salt the cache path so configs with @@ -729,9 +744,7 @@ def convert_libraries( ) def is_ignored(name: str | None) -> bool: - if not lib_ignore or name is None: - return False - return name.split("/")[-1].lower() in lib_ignore + return is_lib_ignored(name, lib_ignore) def add_spec(name: str | None, version: str | None, repository: str | None) -> str: key, kind, locator = _node_key(name, version, repository) diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index 1d51830c9b..813743c7fa 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -249,9 +249,13 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None: assert "-T eagle.flash.4m.ld" in content # scanf float disabled: the forced-link flag must not appear assert "_scanf_float" not in content - # Compile inputs and outputs are quoted (space-containing cache paths) - assert '-c "$in" -o "$out"' in content - assert '--app "$in"' in content + # $in/$out must stay UNQUOTED: ninja shell-escapes its built-in path + # variables itself, so added quotes would wrap ninja's quoting and break + # space-containing paths. + assert "-c $in -o $out" in content + assert "--app $in --flash_mode" in content + assert '"$in"' not in content + assert '"$out"' not in content # -L/-l from esphome build_flags reach the link line, not the compiles assert '-L"/opt/blobs"' in content assert "-luser_blob" in content