Apply review cleanups to the native ESP8266 toolchain

This commit is contained in:
J. Nick Koston
2026-08-19 23:13:30 -05:00
parent 8811224c0e
commit 3fe9cac1fc
12 changed files with 164 additions and 100 deletions
+25 -41
View File
@@ -16,7 +16,16 @@ from unittest.mock import MagicMock, patch
import pytest
from esphome.build_gen import arduino8266
from esphome.build_gen.arduino8266 import (
_defines_flags,
_flag_defines,
_flash_size_str,
_resolve_build_config,
get_flash_ld_path,
)
from esphome.components.esp8266.boards import BOARDS, ESP8266_BOARD_BUILD
from esphome.components.esp8266.build_surgery import RATETABLE_RULE
from esphome.components.esp8266.const import (
KEY_BOARD,
KEY_ESP8266,
@@ -55,7 +64,6 @@ def test_board_build_covers_every_board() -> None:
def test_build_config_defaults() -> None:
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags()
config = _resolve_build_config(_flag_defines())
@@ -75,7 +83,6 @@ def test_build_config_defaults() -> None:
def test_build_config_esphome_lwip_knob() -> None:
"""The lwIP variant ESPHome selects maps to the same defines and library
as the PlatformIO builder."""
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
config = _resolve_build_config(_flag_defines())
@@ -86,7 +93,6 @@ def test_build_config_esphome_lwip_knob() -> None:
def test_build_config_knobs() -> None:
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags(
"-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305",
@@ -102,7 +108,6 @@ def test_build_config_knobs() -> None:
def test_build_config_mmu_custom_requires_sizes() -> None:
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags("-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM")
with pytest.raises(EsphomeError, match="MMU_IRAM_SIZE"):
@@ -122,11 +127,6 @@ def test_build_config_mmu_custom_requires_sizes() -> None:
def test_defines_match_platformio_builder() -> None:
"""The exact define set the PlatformIO builder passes for nodemcuv2/dout."""
from esphome.build_gen.arduino8266 import (
_defines_flags,
_flag_defines,
_resolve_build_config,
)
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
assert _defines_flags(
@@ -183,9 +183,11 @@ def _make_framework(tmp_path: Path) -> dict[str, Path]:
}
def _write_ninja(paths: dict[str, Path]) -> str:
from esphome.build_gen import arduino8266
def _write_ninja(
paths: dict[str, Path],
libraries: list | None = None,
ccache: str | None = None,
) -> str:
src = CORE.relative_src_path()
(src / "esphome" / "components" / "esp8266").mkdir(parents=True, exist_ok=True)
(src / "main.cpp").write_text("")
@@ -193,7 +195,11 @@ def _write_ninja(paths: dict[str, Path]) -> str:
with (
patch.object(arduino8266, "generate_ld_scripts"),
patch("esphome.arduino8266.framework.ccache_path", return_value=None),
patch(
"esphome.arduino8266.component.resolve_libraries",
return_value=libraries or [],
),
patch("esphome.arduino8266.framework.ccache_path", return_value=ccache),
):
arduino8266.write_project(paths)
return (CORE.relative_pioenvs_path(CORE.name) / "build.ninja").read_text()
@@ -284,7 +290,6 @@ def test_build_config_lwip_variants(
knob: str, lib: str, mss: int, features: int, ipv6: int
) -> None:
"""Every lwIP knob maps to the same defines and library as the PIO builder."""
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags(f"-D{knob}")
config = _resolve_build_config(_flag_defines())
@@ -321,14 +326,12 @@ def test_build_config_lwip_variants(
],
)
def test_build_config_mmu_variants(knob: str, expected: list[str]) -> None:
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags(f"-D{knob}")
assert _resolve_build_config(_flag_defines()).mmu_defines == expected
def test_build_config_waveform_locked_phase() -> None:
from esphome.build_gen.arduino8266 import _flag_defines, _resolve_build_config
_set_flags("-DPIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE", "-DFP_IN_IROM")
config = _resolve_build_config(_flag_defines())
@@ -345,16 +348,13 @@ _COMMON_LD_H_OUTPUT = """\
def _run_generate_ld_scripts(paths: dict[str, Path]) -> Path:
from esphome.build_gen import arduino8266
config = arduino8266._resolve_build_config(arduino8266._flag_defines())
config = _resolve_build_config(_flag_defines())
arduino8266.generate_ld_scripts(paths, config, "eagle.flash.4m.ld")
return CORE.relative_pioenvs_path(CORE.name, "ld")
def test_generate_ld_scripts(tmp_path: Path) -> None:
from esphome.build_gen import arduino8266
from esphome.components.esp8266.build_surgery import RATETABLE_RULE
paths = _make_framework(tmp_path)
_set_flags("-DFP_IN_IROM")
@@ -375,7 +375,6 @@ def test_generate_ld_scripts(tmp_path: Path) -> None:
def test_generate_ld_scripts_failure(tmp_path: Path) -> None:
from esphome.build_gen import arduino8266
paths = _make_framework(tmp_path)
result = MagicMock(returncode=1, stderr="nope")
@@ -387,7 +386,6 @@ def test_generate_ld_scripts_failure(tmp_path: Path) -> None:
def test_generate_ld_scripts_testing_mode(tmp_path: Path) -> None:
from esphome.build_gen import arduino8266
paths = _make_framework(tmp_path)
(paths["framework_path"] / "tools" / "sdk" / "ld" / "eagle.flash.4m.ld").write_text(
@@ -403,7 +401,6 @@ def test_generate_ld_scripts_testing_mode(tmp_path: Path) -> None:
def test_write_project_libraries_and_variant(tmp_path: Path) -> None:
from esphome.arduino8266.component import ArduinoLibrary
from esphome.build_gen import arduino8266
paths = _make_framework(tmp_path)
variant_src = paths["framework_path"] / "variants" / "nodemcu" / "variant.cpp"
@@ -423,20 +420,9 @@ def test_write_project_libraries_and_variant(tmp_path: Path) -> None:
)
_set_flags("-DPIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS")
src = CORE.relative_src_path()
(src / "esphome" / "components" / "esp8266").mkdir(parents=True, exist_ok=True)
(src / "main.cpp").write_text("")
with (
patch.object(arduino8266, "generate_ld_scripts"),
patch(
"esphome.arduino8266.component.resolve_libraries",
return_value=[library, headers_only],
),
patch("esphome.arduino8266.framework.ccache_path", return_value="/cc/ccache"),
):
arduino8266.write_project(paths)
content = (CORE.relative_pioenvs_path(CORE.name) / "build.ninja").read_text()
content = _write_ninja(
paths, libraries=[library, headers_only], ccache="/cc/ccache"
)
assert "build libFrameworkArduinoVariant.a: ar" in content
assert "build libMyLib.a: ar" in content
@@ -452,10 +438,9 @@ def test_write_project_libraries_and_variant(tmp_path: Path) -> None:
def test_get_flash_ld_path(tmp_path: Path) -> None:
from esphome.build_gen import arduino8266
CORE.testing_mode = True
assert arduino8266.get_flash_ld_path(tmp_path) == (
assert get_flash_ld_path(tmp_path) == (
tmp_path / "ld" / "testing_eagle.flash.4m.ld"
)
@@ -470,13 +455,12 @@ def test_get_flash_ld_path(tmp_path: Path) -> None:
return_value="3.30102.0",
),
):
assert arduino8266.get_flash_ld_path(tmp_path) == (
assert get_flash_ld_path(tmp_path) == (
tmp_path / "framework" / "tools" / "sdk" / "ld" / "eagle.flash.4m.ld"
)
def test_flash_size_str() -> None:
from esphome.build_gen.arduino8266 import _flash_size_str
assert _flash_size_str("eagle.flash.4m.ld") == "4M"
assert _flash_size_str("eagle.flash.512k.ld") == "512K"
@@ -62,3 +62,10 @@ def test_testing_memory_patches_enlarge_segments() -> None:
"irom0_0_seg : org = 0x40201010, len = 0x2000000"
in patched
)
def test_segment_length() -> None:
from esphome.components.esp8266.build_surgery import segment_length
assert segment_length(_FLASH_LD_SNIPPET, "irom0_0_seg") == 0xFEFF0
assert segment_length(_FLASH_LD_SNIPPET, "missing_seg") is None
@@ -245,12 +245,9 @@ def test_ccache_path_disabled_by_env() -> None:
assert framework.ccache_path() is None
def test_ccache_path_no_binary() -> None:
with (
patch.dict(os.environ, {}, clear=False),
patch("shutil.which", return_value=None),
):
os.environ.pop("ESPHOME_CCACHE_ENABLE", None)
def test_ccache_path_no_binary(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("ESPHOME_CCACHE_ENABLE", raising=False)
with patch("shutil.which", return_value=None):
assert framework.ccache_path() is None
@@ -263,12 +260,12 @@ def test_ccache_path_probe_failure() -> None:
assert framework.ccache_path() is None
def test_ccache_path_ok() -> None:
def test_ccache_path_ok(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("ESPHOME_CCACHE_ENABLE", raising=False)
with (
patch("shutil.which", return_value="/usr/bin/ccache"),
patch("subprocess.run"),
):
os.environ.pop("ESPHOME_CCACHE_ENABLE", None)
assert framework.ccache_path() == "/usr/bin/ccache"
@@ -184,3 +184,30 @@ def test_get_idedata_delegates(tmp_path: Path) -> None:
assert compile_commands.name == "compile_commands.json"
assert elf.name == "firmware.elf"
assert cache.name == "test8266.json"
def test_run_compile_skips_compdb_when_ninja_unchanged(tmp_path: Path) -> None:
"""An unchanged build.ninja means the compile DB is already current."""
build_dir = toolchain.get_build_dir()
build_dir.mkdir(parents=True)
def run(regenerate_expected: bool) -> None:
with (
patch.object(framework, "check_and_install", return_value=_paths(tmp_path)),
patch.object(framework, "get_build_env", return_value={}),
patch("esphome.build_gen.arduino8266.write_project", return_value=False),
patch.object(
toolchain.subprocess, "run", return_value=MagicMock(returncode=0)
),
patch.object(toolchain, "_write_compile_commands") as mock_compdb,
patch.object(toolchain, "_print_size_summary"),
patch.object(toolchain, "get_idedata"),
):
assert toolchain.run_compile({CONF_ESPHOME: {}}, verbose=False) == 0
assert mock_compdb.called == regenerate_expected
# Missing compile DB: regenerated even though build.ninja is unchanged
run(regenerate_expected=True)
# Present compile DB + unchanged build.ninja: skipped
(build_dir / "compile_commands.json").write_text("[]")
run(regenerate_expected=False)
+5 -5
View File
@@ -499,7 +499,7 @@ def test_parse_library_json(tmp_path):
assert result["name"] == "test"
def testparse_library_properties(tmp_path):
def test_parse_library_properties(tmp_path):
f = tmp_path / "library.properties"
f.write_text(
"""
@@ -679,22 +679,22 @@ def test_node_key_registry_bare_name():
assert (key, kind, locator) == ("bar", "registry", (None, "bar"))
def testnormalize_dependencies_none():
def test_normalize_dependencies_none():
assert normalize_dependencies(None) == []
def testnormalize_dependencies_list_form():
def test_normalize_dependencies_list_form():
deps = [{"name": "foo", "version": "1.0"}]
assert normalize_dependencies(deps) == [{"name": "foo", "version": "1.0"}]
def testnormalize_dependencies_dict_form():
def test_normalize_dependencies_dict_form():
out = normalize_dependencies({"nanopb/Nanopb": "^0.4.91", "BareName": "1.2.3"})
assert {"name": "Nanopb", "owner": "nanopb", "version": "^0.4.91"} in out
assert {"name": "BareName", "owner": None, "version": "1.2.3"} in out
def testnormalize_dependencies_dict_form_nested_spec():
def test_normalize_dependencies_dict_form_nested_spec():
out = normalize_dependencies(
{"nanopb/Nanopb": {"version": "^0.4.91", "platforms": "espidf"}}
)
+18
View File
@@ -7168,3 +7168,21 @@ def test_write_cpp_file_arduino_toolchain_writes_no_project(tmp_path: Path) -> N
mock_write_cpp.assert_called_once()
mock_pio_project.assert_not_called()
def test_write_cpp_file_platformio_toolchain_writes_project(tmp_path: Path) -> None:
"""The default toolchain writes the PlatformIO project files."""
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test")
with (
patch("esphome.writer.write_cpp") as mock_write_cpp,
patch("esphome.build_gen.platformio.write_project") as mock_pio_project,
patch.object(
type(CORE), "cpp_main_section", new_callable=PropertyMock
) as mock_section,
):
mock_section.return_value = ""
assert main.write_cpp_file() == 0
mock_write_cpp.assert_called_once()
mock_pio_project.assert_called_once()