mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
Merge branch 'esp8266-native-build-spec' into esp8266-native-ninja-emission
# Conflicts: # esphome/build_gen/arduino8266.py
This commit is contained in:
@@ -48,7 +48,7 @@ from esphome.framework_helpers import (
|
|||||||
get_project_cxx_compile_flags,
|
get_project_cxx_compile_flags,
|
||||||
strip_win_long_path_prefix,
|
strip_win_long_path_prefix,
|
||||||
)
|
)
|
||||||
from esphome.helpers import mkdir_p, write_file, write_file_if_changed
|
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||||
from esphome.platformio.library import SOURCE_KIND_FOR_SUFFIX, lex_build_flags
|
from esphome.platformio.library import SOURCE_KIND_FOR_SUFFIX, lex_build_flags
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -274,6 +274,10 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig:
|
|||||||
# SDK header's #error
|
# SDK header's #error
|
||||||
if unknown := [k for k in vtables_knobs if k not in known_vtables]:
|
if unknown := [k for k in vtables_knobs if k not in known_vtables]:
|
||||||
raise EsphomeError(f"Unknown VTABLES_IN_* define(s): {', '.join(unknown)}")
|
raise EsphomeError(f"Unknown VTABLES_IN_* define(s): {', '.join(unknown)}")
|
||||||
|
# A body (e.g. VTABLES_IN_FLASH=0) would split the compile line from the
|
||||||
|
# linker script, which always defines the bare name
|
||||||
|
if valued := [defines[k] for k in vtables_knobs if defines[k] not in (k, f"{k}=1")]:
|
||||||
|
raise EsphomeError(f"VTABLES_IN_* defines take no value: {', '.join(valued)}")
|
||||||
if len(vtables_knobs) > 1:
|
if len(vtables_knobs) > 1:
|
||||||
raise EsphomeError(
|
raise EsphomeError(
|
||||||
f"Conflicting VTABLES_IN_* defines: {', '.join(vtables_knobs)}"
|
f"Conflicting VTABLES_IN_* defines: {', '.join(vtables_knobs)}"
|
||||||
@@ -377,6 +381,10 @@ def _defines_flags(
|
|||||||
defines embed ``\"``), so they must be emitted unquoted; wrapping
|
defines embed ``\"``), so they must be emitted unquoted; wrapping
|
||||||
them in ``_shell_token`` would deliver literal backslashes to gcc.
|
them in ``_shell_token`` would deliver literal backslashes to gcc.
|
||||||
"""
|
"""
|
||||||
|
if not re.fullmatch(r"[\w.-]+", board):
|
||||||
|
# The name lands unquoted in two -D bodies; reject it by name
|
||||||
|
# instead of corrupting the compile line
|
||||||
|
raise EsphomeError(f"Invalid board name {board!r}")
|
||||||
# Every supported board ships 80 MHz; board_build.f_cpu overrides
|
# Every supported board ships 80 MHz; board_build.f_cpu overrides
|
||||||
f_cpu = _pio_option("board_build.f_cpu", "80000000L")
|
f_cpu = _pio_option("board_build.f_cpu", "80000000L")
|
||||||
if not re.fullmatch(r"\d+L?", f_cpu):
|
if not re.fullmatch(r"\d+L?", f_cpu):
|
||||||
@@ -496,6 +504,21 @@ def _stat_sig(path: Path) -> str:
|
|||||||
return f"unreadable:{os.urandom(8).hex()}"
|
return f"unreadable:{os.urandom(8).hex()}"
|
||||||
|
|
||||||
|
|
||||||
|
def _write_generated(path: Path, content: str) -> None:
|
||||||
|
"""write_file_if_changed, replacing an unreadable existing copy.
|
||||||
|
|
||||||
|
The recovery is scoped to the comparison read: a damaged cached file is
|
||||||
|
logged and overwritten, while a genuine write failure still raises.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if path.is_file():
|
||||||
|
path.read_text(encoding="utf-8")
|
||||||
|
except (OSError, UnicodeDecodeError) as err:
|
||||||
|
_LOGGER.warning("Replacing damaged generated file %s: %s", path, err)
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
write_file_if_changed(path, content)
|
||||||
|
|
||||||
|
|
||||||
def generate_ld_scripts(
|
def generate_ld_scripts(
|
||||||
paths: InstalledPaths, config: _BuildConfig, flash_ld_name: str
|
paths: InstalledPaths, config: _BuildConfig, flash_ld_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -505,6 +528,9 @@ def generate_ld_scripts(
|
|||||||
``eagle.app.v6.common.ld.h``, then applies ESPHome's surgeries: the wifi
|
``eagle.app.v6.common.ld.h``, then applies ESPHome's surgeries: the wifi
|
||||||
rate-table DRAM relocation, and enlarged memory segments in testing mode.
|
rate-table DRAM relocation, and enlarged memory segments in testing mode.
|
||||||
"""
|
"""
|
||||||
|
if not re.fullmatch(r"[\w.-]+\.ld", flash_ld_name):
|
||||||
|
# Joined under the SDK and build ld dirs; never a path or traversal
|
||||||
|
raise EsphomeError(f"Invalid flash linker script name {flash_ld_name!r}")
|
||||||
framework = paths.framework
|
framework = paths.framework
|
||||||
gcc = toolchain_tool(paths.toolchain, "gcc")
|
gcc = toolchain_tool(paths.toolchain, "gcc")
|
||||||
ld_dir = CORE.relative_pioenvs_path(CORE.name, "ld")
|
ld_dir = CORE.relative_pioenvs_path(CORE.name, "ld")
|
||||||
@@ -591,13 +617,7 @@ def generate_ld_scripts(
|
|||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
# Same changed-linker-script failure class as the ratetable
|
# Same changed-linker-script failure class as the ratetable
|
||||||
raise EsphomeError(str(err)) from err
|
raise EsphomeError(str(err)) from err
|
||||||
try:
|
_write_generated(output, content)
|
||||||
write_file_if_changed(output, content)
|
|
||||||
except EsphomeError:
|
|
||||||
# A non-UTF-8/unreadable cached script fails the comparison
|
|
||||||
# read; regeneration must overwrite it, not abort
|
|
||||||
output.unlink(missing_ok=True)
|
|
||||||
write_file(output, content)
|
|
||||||
stamp.write_text(
|
stamp.write_text(
|
||||||
f"{stamp_content} content={hashlib.sha256(content.encode('utf-8')).hexdigest()}",
|
f"{stamp_content} content={hashlib.sha256(content.encode('utf-8')).hexdigest()}",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
@@ -635,7 +655,7 @@ def generate_ld_scripts(
|
|||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
# Same changed-linker-script failure class as the ratetable
|
# Same changed-linker-script failure class as the ratetable
|
||||||
raise EsphomeError(str(err)) from err
|
raise EsphomeError(str(err)) from err
|
||||||
write_file_if_changed(ld_dir / f"testing_{flash_ld_name}", patched_flash_ld)
|
_write_generated(ld_dir / f"testing_{flash_ld_name}", patched_flash_ld)
|
||||||
|
|
||||||
|
|
||||||
def _ninja_compile_edges(
|
def _ninja_compile_edges(
|
||||||
|
|||||||
@@ -1071,6 +1071,41 @@ def test_generate_ld_scripts_unreadable_stamp_regenerates(tmp_path: Path) -> Non
|
|||||||
mock_run.assert_called_once()
|
mock_run.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_vtables_valued_define_raises() -> None:
|
||||||
|
"""A VTABLES_IN_* body would split the compile line from the linker
|
||||||
|
script, which always defines the bare name."""
|
||||||
|
with pytest.raises(EsphomeError, match="take no value.*VTABLES_IN_FLASH=0"):
|
||||||
|
_resolve("-DVTABLES_IN_FLASH=0")
|
||||||
|
|
||||||
|
|
||||||
|
def test_defines_flags_invalid_board_raises() -> None:
|
||||||
|
"""The board name lands unquoted in two -D bodies; reject it by name."""
|
||||||
|
with pytest.raises(EsphomeError, match="Invalid board name"):
|
||||||
|
_defines_flags(_resolve(), "dout", "evil board", ())
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_ld_scripts_invalid_flash_ld_name_raises(tmp_path: Path) -> None:
|
||||||
|
"""The script name joins under the SDK and build ld dirs; a traversal
|
||||||
|
or path is rejected by name."""
|
||||||
|
paths = _make_framework(tmp_path)
|
||||||
|
_set_flags()
|
||||||
|
config = _resolve()
|
||||||
|
with pytest.raises(EsphomeError, match="Invalid flash linker script name"):
|
||||||
|
arduino8266.generate_ld_scripts(paths, config, "../evil.ld")
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_generated_replaces_damaged_file(
|
||||||
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""A non-UTF-8 existing copy is logged and overwritten; the write path
|
||||||
|
still raises for real failures."""
|
||||||
|
target = tmp_path / "gen.ld"
|
||||||
|
target.write_bytes(b"\xff\xfe")
|
||||||
|
arduino8266._write_generated(target, "SECTIONS { }")
|
||||||
|
assert target.read_text(encoding="utf-8") == "SECTIONS { }"
|
||||||
|
assert "Replacing damaged generated file" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_generate_ld_scripts_edited_output_regenerates(tmp_path: Path) -> None:
|
def test_generate_ld_scripts_edited_output_regenerates(tmp_path: Path) -> None:
|
||||||
"""The stamp records the content hash, so an externally edited cached
|
"""The stamp records the content hash, so an externally edited cached
|
||||||
script regenerates instead of linking untrusted content."""
|
script regenerates instead of linking untrusted content."""
|
||||||
|
|||||||
Reference in New Issue
Block a user