mirror of
https://github.com/esphome/esphome.git
synced 2026-09-12 15:57:33 +00:00
Merge branch 'esp8266-native-ninja-emission' into esp8266-arduino-toolchain
This commit is contained in:
@@ -320,15 +320,18 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig:
|
||||
"PIO_FRAMEWORK_ARDUINO_MMU_CUSTOM requires MMU_IRAM_SIZE and "
|
||||
"MMU_ICACHE_SIZE build flags"
|
||||
)
|
||||
for size_name in ("MMU_IRAM_SIZE", "MMU_ICACHE_SIZE"):
|
||||
# These reach the linker-script preprocessor; a bare or
|
||||
# non-numeric value would corrupt the segment lengths and fail
|
||||
# far away in ld ("48K" and a valueless flag both preprocess
|
||||
# wrong; ul suffixes survive preprocessing, see build_surgery)
|
||||
value = defines[size_name].partition("=")[2]
|
||||
if not re.fullmatch(r"(?:0[xX][0-9a-fA-F]+|\d+)[uUlL]*", value):
|
||||
for name, body in defines.items():
|
||||
if not name.startswith("MMU_") or "=" not in body:
|
||||
# Valueless flags (MMU_IRAM_HEAP) are legitimate switches
|
||||
continue
|
||||
# Every valued MMU_* reaches the linker-script preprocessor; a
|
||||
# bare or non-numeric value would corrupt the segment lengths
|
||||
# and fail far away in ld. Hex only: build_surgery's segment
|
||||
# parser (and upstream's spellings) cannot read decimal.
|
||||
value = body.partition("=")[2]
|
||||
if not re.fullmatch(r"0[xX][0-9a-fA-F]+[uUlL]*", value):
|
||||
raise EsphomeError(
|
||||
f"{size_name} must be a numeric literal, got "
|
||||
f"{name} must be a hex literal (e.g. 0x8000), got "
|
||||
f"{value or '(no value)'}"
|
||||
)
|
||||
# Sorted so build.ninja and the linker-script stamp stay
|
||||
@@ -336,11 +339,12 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig:
|
||||
# iteration order).
|
||||
mmu = sorted(body for name, body in defines.items() if name.startswith("MMU_"))
|
||||
else:
|
||||
if "MMU_IRAM_SIZE" in defines or "MMU_ICACHE_SIZE" in defines:
|
||||
if raw := sorted(n for n in defines if n.startswith("MMU_")):
|
||||
# Unlike PlatformIO (whose defaults win the compile line), user
|
||||
# MMU_* here would win the compile but not the linker script; refuse.
|
||||
# MMU_* here would win the compile but not the linker script;
|
||||
# refuse them all, like the knob branch above.
|
||||
raise EsphomeError(
|
||||
"Custom MMU_IRAM_SIZE/MMU_ICACHE_SIZE build flags require "
|
||||
f"Raw {', '.join(raw)} build flags require "
|
||||
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM"
|
||||
)
|
||||
mmu = list(_MMU_DEFAULT)
|
||||
|
||||
@@ -16,7 +16,11 @@ def tools_cache_path(env_var: str, subdir: str) -> Path:
|
||||
from esphome.helpers import get_str_env
|
||||
|
||||
if prefix := get_str_env(env_var, "").strip():
|
||||
# resolve(): symlinked prefixes otherwise trip idf.py's
|
||||
# venv-mismatch warning on every build
|
||||
return Path(prefix).expanduser().resolve()
|
||||
# appauthor=False keeps the Windows path short (no vendor segment);
|
||||
# deep IDF trees run into MAX_PATH otherwise
|
||||
return (
|
||||
Path(platformdirs.user_cache_dir("esphome", appauthor=False)) / subdir
|
||||
).resolve()
|
||||
|
||||
+3
-2
@@ -31,8 +31,9 @@ SockAddr = IPv4SockAddr | IPv6SockAddr
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# cv.boolean's closed spelling tables; shared so env-knob parsers cannot
|
||||
# drift from what configs accept
|
||||
# cv.boolean's closed spelling tables, shared with the strict env-knob
|
||||
# parser (build_helpers.ccache.parse_enable_env). The legacy get_bool_env
|
||||
# below keeps its own laxer table for backward compatibility.
|
||||
TRUTHY_BOOL_STRINGS = frozenset({"true", "yes", "on", "enable"})
|
||||
FALSY_BOOL_STRINGS = frozenset({"false", "no", "off", "disable"})
|
||||
|
||||
|
||||
@@ -110,6 +110,11 @@ class _FakeSConsEnv:
|
||||
def get(self, key: str, default: str | None = None) -> str | None:
|
||||
return self._vars.get(key, default)
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
# Scripts also read env["BOARD_MCU"]; without this the broad
|
||||
# handler would discard every flag the script captured
|
||||
return self._vars[key]
|
||||
|
||||
def Append(self, **kwargs) -> None: # noqa: N802 (SCons API name)
|
||||
for key, value in kwargs.items():
|
||||
if key not in _CAPTURED_KEYS:
|
||||
|
||||
@@ -211,8 +211,14 @@ def prefetch_packages(
|
||||
[(entry[0], _fetch(entry)) for entry in pending],
|
||||
)
|
||||
for name, err in failures:
|
||||
# install_package retries this one itself, with a visible bar
|
||||
_LOGGER.debug("Prefetch of %s failed: %s", name, err)
|
||||
if isinstance(err, (EsphomeError, OSError)):
|
||||
# Expected download failures: install_package retries this one
|
||||
# itself, with a visible bar
|
||||
_LOGGER.debug("Prefetch of %s failed: %s", name, err)
|
||||
else:
|
||||
# Anything else is a programming error that would otherwise
|
||||
# become a permanent silent no-op
|
||||
_LOGGER.warning("Prefetch of %s failed: %r", name, err)
|
||||
|
||||
|
||||
def install_package(
|
||||
|
||||
@@ -1142,7 +1142,7 @@ def test_generate_ld_scripts_unreadable_note_still_warns(
|
||||
assert "could not be read" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0x8000", "0xC000ul", "32768", "48UL"])
|
||||
@pytest.mark.parametrize("value", ["0x8000", "0xC000ul", "0x10UL"])
|
||||
def test_mmu_custom_numeric_sizes_accepted(value: str) -> None:
|
||||
config = _resolve(
|
||||
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
|
||||
@@ -1152,11 +1152,19 @@ def test_mmu_custom_numeric_sizes_accepted(value: str) -> None:
|
||||
assert f"MMU_IRAM_SIZE={value}" in config.mmu_defines
|
||||
|
||||
|
||||
@pytest.mark.parametrize("flag", ["-DMMU_IRAM_SIZE=48K", "-DMMU_IRAM_SIZE"])
|
||||
@pytest.mark.parametrize(
|
||||
"flag",
|
||||
[
|
||||
"-DMMU_IRAM_SIZE=48K",
|
||||
# Decimal passes preprocessing but build_surgery's segment parser
|
||||
# only reads hex, so testing-mode surgery would fail misleadingly
|
||||
"-DMMU_IRAM_SIZE=32768",
|
||||
],
|
||||
)
|
||||
def test_mmu_custom_malformed_size_raises(flag: str) -> None:
|
||||
"""A bare or non-numeric size would corrupt the preprocessed segment
|
||||
lengths and fail far away in ld; refuse by name."""
|
||||
with pytest.raises(EsphomeError, match="MMU_IRAM_SIZE must be a numeric"):
|
||||
"""A non-hex size would corrupt the preprocessed segment lengths (or
|
||||
defeat the testing-mode surgery); refuse by name."""
|
||||
with pytest.raises(EsphomeError, match="MMU_IRAM_SIZE must be a hex"):
|
||||
_resolve(
|
||||
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
|
||||
flag,
|
||||
@@ -1164,6 +1172,32 @@ def test_mmu_custom_malformed_size_raises(flag: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_mmu_custom_valueless_switch_accepted_and_others_validated() -> None:
|
||||
"""Valueless MMU switches (MMU_IRAM_HEAP) pass; every valued MMU_* is
|
||||
hex-validated, not just the two required sizes."""
|
||||
config = _resolve(
|
||||
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
|
||||
"-DMMU_IRAM_SIZE=0x8000",
|
||||
"-DMMU_ICACHE_SIZE=0x8000",
|
||||
"-DMMU_IRAM_HEAP",
|
||||
)
|
||||
assert "MMU_IRAM_HEAP" in config.mmu_defines
|
||||
with pytest.raises(EsphomeError, match="MMU_SEC_HEAP_SIZE must be a hex"):
|
||||
_resolve(
|
||||
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
|
||||
"-DMMU_IRAM_SIZE=0x8000",
|
||||
"-DMMU_ICACHE_SIZE=0x8000",
|
||||
"-DMMU_SEC_HEAP_SIZE=48K",
|
||||
)
|
||||
|
||||
|
||||
def test_mmu_no_knob_rejects_any_raw_mmu_flag() -> None:
|
||||
"""The no-knob branch refuses every raw MMU_*, like the knob branch; a
|
||||
lone switch would win the compile line but not the linker script."""
|
||||
with pytest.raises(EsphomeError, match="Raw MMU_IRAM_HEAP"):
|
||||
_resolve("-DMMU_IRAM_HEAP")
|
||||
|
||||
|
||||
def test_raw_nonosdk_define_raises() -> None:
|
||||
"""A raw NONOSDK* define would split the compile line from the linked
|
||||
SDK libraries, like the lwIP knob overrides."""
|
||||
|
||||
@@ -169,6 +169,22 @@ def test_apply_extra_script_callable_target_and_str_flags(tmp_path) -> None:
|
||||
assert c.data["build"]["flags"] == ["-DBASE=1", "-lesp8266"]
|
||||
|
||||
|
||||
def test_apply_extra_script_subscript_env_read(tmp_path) -> None:
|
||||
"""Scripts also read env["BOARD_MCU"]; the subscript form must work or
|
||||
the broad handler discards every flag the script captured."""
|
||||
(tmp_path / "src").mkdir()
|
||||
script = tmp_path / "extra.py"
|
||||
script.write_text("env.Append(LIBS=[env['BOARD_MCU']])\n")
|
||||
|
||||
c = IDFComponent("owner/name", "1.0", source=URLSource("http://dummy"))
|
||||
c.path = tmp_path
|
||||
c.data = {"build": {"extraScript": "extra.py"}}
|
||||
|
||||
apply_extra_script(c, board_mcu=lambda: "esp8266", pio_platform="espressif8266")
|
||||
|
||||
assert c.data["build"]["flags"] == ["-lesp8266"]
|
||||
|
||||
|
||||
def test_apply_extra_script_no_script_and_no_flags(tmp_path) -> None:
|
||||
|
||||
# No extraScript declared: nothing happens, the target is never resolved
|
||||
|
||||
@@ -166,9 +166,13 @@ def caplog_at_info():
|
||||
handler.emit = records.append
|
||||
logger = logging.getLogger("esphome.platformio.library")
|
||||
logger.addHandler(handler)
|
||||
# The level must actually admit INFO or the no-INFO assertions are vacuous
|
||||
old_level = logger.level
|
||||
logger.setLevel(logging.INFO)
|
||||
try:
|
||||
yield records
|
||||
finally:
|
||||
logger.setLevel(old_level)
|
||||
logger.removeHandler(handler)
|
||||
|
||||
|
||||
|
||||
@@ -625,3 +625,26 @@ def test_prefetch_packages_download_failure_is_debug(
|
||||
assert mock_download.call_count == 2
|
||||
assert "Prefetch of a failed" in caplog.text
|
||||
assert "Prefetch of b failed" in caplog.text
|
||||
|
||||
|
||||
def test_prefetch_packages_unexpected_failure_warns(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A programming error (not a download failure) surfaces at WARNING
|
||||
instead of becoming a permanent silent no-op."""
|
||||
with (
|
||||
patch.object(
|
||||
registry, "download_with_resume", side_effect=TypeError("bad call")
|
||||
),
|
||||
patch.object(
|
||||
registry, "registry_download", side_effect=_resolve_for({"a": 10, "b": 20})
|
||||
),
|
||||
):
|
||||
registry.prefetch_packages(
|
||||
[
|
||||
("a", "1.0", tmp_path / "a", []),
|
||||
("b", "2.0", tmp_path / "b", []),
|
||||
],
|
||||
tmp_path / "dl",
|
||||
)
|
||||
assert "TypeError" in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user