diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 9b3ecb007a..0a47cdf178 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -631,6 +631,10 @@ _PLAIN_LINKER_FLAGS = ( "-nostdlib", "-rdynamic", ) +# The subset whose next token is an operand; unflagging the bare flag +# would strand the operand. Operand-less members of the list above filter +# whole-token from both the compile and link lines, as PlatformIO allows. +_PLAIN_LINKER_OPERAND_FLAGS = ("-u", "-e") _PLAIN_LINKER_PREFIXES = ("-T", "-Xlinker") # Driver options, not ld options: -Wl, has no equivalent for these _PLAIN_DRIVER_LINK_PREFIXES = ("-fuse-ld=", "--specs=", "-specs=") @@ -993,7 +997,7 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool: if plain := sorted( u for u in unflags - if u in _PLAIN_LINKER_FLAGS or u.startswith(_PLAIN_LINKER_PREFIXES) + if u in _PLAIN_LINKER_OPERAND_FLAGS or u.startswith(_PLAIN_LINKER_PREFIXES) ): raise EsphomeError( f"build_unflags cannot remove plain linker flag(s) " @@ -1040,7 +1044,7 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool: f"buildtool = {_q(build_tool)}", f"ccache = {_q(ccache) if ccache else ''}", "", - # Rule names match SOURCE_KIND_FOR_SUFFIX values (c, cxx, asm) + # Rule names match SOURCE_KIND_FOR_SUFFIX values (c, cxx, asm, aspp) "rule c", " command = $ccache $cc -MMD -MF $out.d $cflags $flags -c $in -o $out", " depfile = $out.d", @@ -1051,11 +1055,16 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool: " depfile = $out.d", " deps = gcc", " description = CXX $out", - "rule asm", + "rule aspp", " 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", + # Plain assembler, as SCons's ASCOM: no preprocessor, so no + # depfile and no $flags (defines/includes) either + "rule asm", + " command = $ccache $cc -x assembler $asflags -c $in -o $out", + " description = AS $out", "rule ar", f" command = $python $buildtool ar {_q(toolchain_tool(paths.toolchain, 'ar'))} $out $out.rsp", " rspfile = $out.rsp", diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index 89943c8aaa..2bbf48e77d 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -55,8 +55,9 @@ DEFAULT_BUILD_SRC_DIRS = "src" DEFAULT_BUILD_INCLUDE_DIR = "include" DEFAULT_BUILD_FLAGS = [] # Suffix -> compiler kind (PlatformIO's CSUFFIXES/CXXSUFFIXES/ASSUFFIXES); -# "asm" merges SCons's AS and ASPP sets. Per CXXSUFFIXES .C/.C++ are C++ -# here, even where SCons demotes .C on case-insensitive filesystems. +# "aspp" is SCons's preprocessed-assembly set, "asm" its plain-assembler +# set (no preprocessor, defines, or includes). Per CXXSUFFIXES .C/.C++ are +# C++ here, even where SCons demotes .C on case-insensitive filesystems. SOURCE_KIND_FOR_SUFFIX: dict[str, str] = { ".c": "c", ".cpp": "cxx", @@ -65,10 +66,10 @@ SOURCE_KIND_FOR_SUFFIX: dict[str, str] = { ".c++": "cxx", ".C": "cxx", ".C++": "cxx", - ".S": "asm", - ".spp": "asm", - ".SPP": "asm", - ".sx": "asm", + ".S": "aspp", + ".spp": "aspp", + ".SPP": "aspp", + ".sx": "aspp", ".s": "asm", ".asm": "asm", ".ASM": "asm", diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index 2ea5dca8aa..947778e400 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -346,7 +346,7 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None: assert "core_esp8266_waveform_phase.cpp" not in content assert "core_esp8266_main.cpp.o" in content # Assembly and C sources compile through their own rules - assert "cont.S.o: asm" in content + assert "cont.S.o: aspp" in content assert "abi.c.o: c" in content # throw_stubs is force-included for ESPHome sources only, via one shared # srcflags variable rather than a copy of the flags line per edge @@ -688,6 +688,30 @@ def test_build_config_nonosdk_precedence() -> None: assert _resolve_build_config(_defines()).nonosdk == "NONOSDK221" +def test_write_project_plain_asm_rule_skips_preprocessor(tmp_path: Path) -> None: + """A lowercase .s source assembles plain (SCons AS), never through the + preprocessor rule that a .S source gets.""" + paths = _make_framework(tmp_path) + core_dir = paths.framework / "cores" / "esp8266" + (core_dir / "lowlevel.s").write_text("nop\n") + _set_flags() + content = _write_ninja(paths) + assert "lowlevel.s.o: asm " in content + assert "rule asm\n command = $ccache $cc -x assembler $asflags -c $in -o $out" in ( + content + ) + + +def test_write_project_unflags_operandless_linker_flag(tmp_path: Path) -> None: + """build_unflags: -nostdlib filters whole-token from both lines, as + PlatformIO allows; only operand-taking flags hard-error.""" + paths = _make_framework(tmp_path) + _set_flags() + CORE.build_unflags = {"-nostdlib"} + content = _write_ninja(paths) + assert "-nostdlib" not in content + + def test_write_project_unflagged_symbol_takes_its_dash_u(tmp_path: Path) -> None: """Unflagging a -u symbol drops the -u that carried it; a dangling -u would consume the next token and hand ld a symbol as an input file.""" diff --git a/tests/unit_tests/test_platformio_library.py b/tests/unit_tests/test_platformio_library.py index 0d207d6951..653cf26c87 100644 --- a/tests/unit_tests/test_platformio_library.py +++ b/tests/unit_tests/test_platformio_library.py @@ -909,12 +909,12 @@ def test_split_flag_entry_non_string_is_clean() -> None: def test_source_kind_map_shape() -> None: - """The kind values the native compile rules key on, and the deliberate - AS/ASPP merge (.s and .S both map to asm).""" + """The kind values the native compile rules key on; the AS/ASPP split + matches SCons (.S preprocessed, .s plain assembler).""" - assert set(SOURCE_KIND_FOR_SUFFIX.values()) == {"c", "cxx", "asm"} + assert set(SOURCE_KIND_FOR_SUFFIX.values()) == {"c", "cxx", "asm", "aspp"} assert SOURCE_KIND_FOR_SUFFIX[".s"] == "asm" - assert SOURCE_KIND_FOR_SUFFIX[".S"] == "asm" + assert SOURCE_KIND_FOR_SUFFIX[".S"] == "aspp" assert SOURCE_KIND_FOR_SUFFIX[".c"] == "c" assert SOURCE_KIND_FOR_SUFFIX[".cpp"] == "cxx" # SCons's case-sensitive C++ suffixes: PIO compiles .C as C++