mirror of
https://github.com/esphome/esphome.git
synced 2026-09-04 12:06:01 +00:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6446fdb5f | ||
|
|
bc1c91a5f7 | ||
|
|
a5701c43ba | ||
|
|
b81d250737 | ||
|
|
6d1d439d7b | ||
|
|
cdd89dbfed | ||
|
|
de7f9e3d93 | ||
|
|
150f0a3b6c | ||
|
|
6a6a6679bc | ||
|
|
4af285a5a0 | ||
|
|
0b0f86527d | ||
|
|
168d96d9f4 | ||
|
|
9b1223b700 | ||
|
|
94fc9ec63a | ||
|
|
5c970df827 | ||
|
|
e51f711883 | ||
|
|
853ef14f10 | ||
|
|
addcb472af | ||
|
|
b97ba73757 | ||
|
|
b5c78a4711 | ||
|
|
5f9af0cfa5 | ||
|
|
5bc3102c82 | ||
|
|
640f2de767 | ||
|
|
c4c6fe198e | ||
|
|
f66eb89f28 | ||
|
|
a4136c8a17 | ||
|
|
de0f706da4 | ||
|
|
41d68d2978 | ||
|
|
b367238273 | ||
|
|
5ab690cca6 | ||
|
|
7d4e28b5e2 | ||
|
|
b4d6633b19 | ||
|
|
8180134d30 | ||
|
|
92b71f8ce4 | ||
|
|
4605ac08a5 | ||
|
|
9580fd14da | ||
|
|
364c530832 | ||
|
|
a0c3a010e4 | ||
|
|
fd871b8898 |
@@ -21,6 +21,7 @@ on:
|
||||
- "esphome/core/**"
|
||||
- "esphome/writer.py"
|
||||
- "esphome/build_gen/**"
|
||||
- "esphome/build_helpers/**"
|
||||
- "esphome/espidf/**"
|
||||
- "esphome/platformio/**"
|
||||
- "esphome/components/bk72xx/**"
|
||||
@@ -210,7 +211,7 @@ jobs:
|
||||
- host
|
||||
# Strict by default so a new matrix id cannot silently join in the
|
||||
# degrade-quietly mode the knob exists to catch; the knob is inert
|
||||
# where no pch code runs (esp32-*-platformio, nrf52, host).
|
||||
# where no pch code runs (nrf52).
|
||||
# Opt-outs: libretiny GCC rejects its own pch until a toolchain bump.
|
||||
include:
|
||||
- id: bk72xx-arduino
|
||||
|
||||
@@ -65,6 +65,10 @@ PCH_ARTIFACT_NAMES = (
|
||||
# The core defines header every backend anchors its prefix on.
|
||||
PCH_CORE_HEADER = "esphome/core/defines.h"
|
||||
|
||||
# Guarded curated-prefix wrapper for PlatformIO backends without framework
|
||||
# force-includes (host, esp32); folded by the pch script via build_src_flags.
|
||||
PCH_PREFIX_HEADER = "esphome/core/pch_prefix.h"
|
||||
|
||||
# Prefix-header contents for backends that inject a curated set (rather
|
||||
# than mirroring the TUs' own force-includes), defines.h first so USE_*
|
||||
# macros exist for the rest. Deliberately hard-coded: frequency-derived
|
||||
|
||||
@@ -10,6 +10,7 @@ import subprocess
|
||||
from typing import Any
|
||||
|
||||
from esphome import yaml_util
|
||||
from esphome.build_helpers.pch import PCH_PREFIX_HEADER, pch_enabled, pch_extra_scripts
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_ENABLE_OTA_DOWNGRADE_PROTECTION
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
@@ -57,6 +58,7 @@ from esphome.coroutine import CoroPriority, coroutine_with_priority
|
||||
from esphome.espidf.component import generate_idf_components
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, rmtree, write_file_if_changed
|
||||
from esphome.platformio.toolchain import copy_pch_script
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
from esphome.writer import clean_build, clean_cmake_cache
|
||||
@@ -2455,6 +2457,11 @@ async def to_code(config):
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
# CI-speed only: this toolchain is being dropped, so the pch gets
|
||||
# the same curated prefix as host with no further investment
|
||||
cg.add_platformio_option("extra_scripts", pch_extra_scripts())
|
||||
if pch_enabled():
|
||||
cg.add_platformio_option("build_src_flags", f"-include {PCH_PREFIX_HEADER}")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
|
||||
@@ -3372,6 +3379,8 @@ def _write_idf_component_yml():
|
||||
def copy_files():
|
||||
_write_sdkconfig()
|
||||
_write_idf_component_yml()
|
||||
if not CORE.using_toolchain_esp_idf:
|
||||
copy_pch_script()
|
||||
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
flash_size = CORE.data[KEY_ESP32][KEY_FLASH_SIZE]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from esphome.build_helpers.pch import PCH_PREFIX_HEADER, pch_enabled, pch_extra_scripts
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -10,7 +11,7 @@ from esphome.const import (
|
||||
ThreadModel,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.platformio.toolchain import copy_ccache_script
|
||||
from esphome.platformio.toolchain import copy_ccache_script, copy_pch_script
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import KEY_HOST
|
||||
@@ -55,9 +56,18 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
cg.add_platformio_option("extra_scripts", ["pre:ccache.py"])
|
||||
cg.add_platformio_option("extra_scripts", ["pre:ccache.py", *pch_extra_scripts()])
|
||||
if pch_enabled():
|
||||
# Curated prefix for the pch (the script folds it plus defines.h):
|
||||
# host has no framework force-includes, and the per-TU cost is the
|
||||
# STL closure behind the core headers. Measured -43% compile CPU.
|
||||
# Gated so ESPHOME_PCH_ENABLE=0 restores the strict view. When the
|
||||
# .gch fails to build or load, the force-include stays and every TU
|
||||
# parses the closure as text: correct, but slower than no pch.
|
||||
cg.add_platformio_option("build_src_flags", f"-include {PCH_PREFIX_HEADER}")
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files() -> None:
|
||||
copy_ccache_script()
|
||||
copy_pch_script()
|
||||
|
||||
@@ -1143,13 +1143,14 @@ class EsphomeCore:
|
||||
def add_platformio_option(
|
||||
self, key: str, value: str | list[str], *, replace: bool = False
|
||||
) -> None:
|
||||
"""Set a platformio.ini option; list values append to an existing list
|
||||
unless ``replace`` is True, which overwrites any existing value."""
|
||||
"""Set a platformio.ini option; values append to an existing list
|
||||
(a string as one element) unless ``replace`` is True, which
|
||||
overwrites any existing value."""
|
||||
new_val = value
|
||||
old_val = self.platformio_options.get(key)
|
||||
if not replace and isinstance(old_val, list):
|
||||
assert isinstance(value, list)
|
||||
new_val = old_val + value
|
||||
# A user platformio_options string must merge, not assert
|
||||
new_val = old_val + ([value] if isinstance(value, str) else value)
|
||||
self.platformio_options[key] = new_val
|
||||
|
||||
def _get_variable_generator(self, id):
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
// Curated precompiled-header prefix for backends that force-include it via
|
||||
// build_src_flags (the pch script folds it into the .gch). Guarded because
|
||||
// build_src_flags also reaches C and assembly src edges.
|
||||
#ifdef __cplusplus
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#endif
|
||||
+2
-1
@@ -7,7 +7,7 @@ import re
|
||||
import time
|
||||
|
||||
from esphome import loader
|
||||
from esphome.build_helpers.pch import PCH_ARTIFACT_NAMES
|
||||
from esphome.build_helpers.pch import PCH_ARTIFACT_NAMES, PCH_PREFIX_HEADER
|
||||
from esphome.compiled_config import save_compiled_config
|
||||
from esphome.config import iter_component_configs, iter_components
|
||||
from esphome.const import (
|
||||
@@ -247,6 +247,7 @@ def copy_src_tree():
|
||||
Path(
|
||||
"esphome/core/ring_buffer.h"
|
||||
), # moved to components/ring_buffer/, removed in 2026.11.0
|
||||
Path(PCH_PREFIX_HEADER), # build machinery, not user API
|
||||
}
|
||||
include_l = []
|
||||
for target, _ in source_files_l:
|
||||
|
||||
@@ -543,6 +543,11 @@ _SMOKE_HARNESS_TRIGGER_FILES = frozenset(
|
||||
|
||||
ESP32_PLATFORMIO_TRIGGER_FILES = _SMOKE_HARNESS_TRIGGER_FILES | {
|
||||
"esphome/build_gen/platformio.py",
|
||||
# The pch machinery the strict smoke job polices, and the modules it
|
||||
# imports; the rest of build_helpers/ does not affect PlatformIO builds
|
||||
"esphome/build_helpers/pch.py",
|
||||
"esphome/build_helpers/ccache.py",
|
||||
"esphome/build_helpers/idedata.py",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1013,6 +1013,10 @@ _ESP32_PLATFORMIO_FULL_LIST_FILES = [
|
||||
# PlatformIO subsystem (path-prefix trigger) + build generator
|
||||
["esphome/platformio/runner.py"],
|
||||
["esphome/platformio/toolchain.py"],
|
||||
# The pch modules are standalone triggers, not the whole build_helpers/
|
||||
["esphome/build_helpers/pch.py"],
|
||||
["esphome/build_helpers/ccache.py"],
|
||||
["esphome/build_helpers/idedata.py"],
|
||||
["esphome/build_gen/platformio.py"],
|
||||
# Workflow / harness files
|
||||
["script/test_build_components.py"],
|
||||
@@ -1071,6 +1075,9 @@ def test_esp32_platformio_components_to_test_returns_full_list_on_infrastructure
|
||||
# Non-PlatformIO files in esphome/build_gen/ do NOT trigger the
|
||||
# full list -- only esphome/build_gen/platformio.py is a trigger.
|
||||
(["esphome/build_gen/espidf.py"], [], []),
|
||||
# build_helpers modules the pch does not import are not triggers.
|
||||
(["esphome/build_helpers/size_summary.py"], [], []),
|
||||
(["esphome/build_helpers/ninja.py"], [], []),
|
||||
# Docs / unrelated files -> empty.
|
||||
(["README.md"], [], []),
|
||||
([], [], []),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""The pch script must reach PlatformIO builds only; the native ESP-IDF
|
||||
toolchain has its own pch flow in build_gen/espidf.py."""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import esp32
|
||||
from esphome.const import Toolchain
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("toolchain", "copied"),
|
||||
[(Toolchain.PLATFORMIO, True), (Toolchain.ESP_IDF, False)],
|
||||
)
|
||||
def test_copy_files_gates_pch_script_on_toolchain(
|
||||
toolchain: Toolchain, copied: bool, tmp_path: Path
|
||||
) -> None:
|
||||
CORE.toolchain = toolchain
|
||||
CORE.build_path = tmp_path
|
||||
from esphome.components.esp32 import (
|
||||
KEY_ESP32,
|
||||
KEY_EXTRA_BUILD_FILES,
|
||||
KEY_FLASH_SIZE,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(esp32, "_write_sdkconfig") as write_sdkconfig,
|
||||
patch.object(esp32, "_write_idf_component_yml"),
|
||||
patch.object(esp32, "copy_pch_script") as copy_script,
|
||||
patch.object(esp32, "write_file_if_changed"),
|
||||
patch.object(esp32, "get_partition_csv"),
|
||||
patch.dict(
|
||||
CORE.data,
|
||||
{KEY_ESP32: {KEY_EXTRA_BUILD_FILES: {}, KEY_FLASH_SIZE: "4MB"}},
|
||||
),
|
||||
):
|
||||
esp32.copy_files()
|
||||
# Proves execution reached (and passed) the gate on both rows
|
||||
assert write_sdkconfig.called
|
||||
assert copy_script.called is copied
|
||||
@@ -568,6 +568,16 @@ class TestEsphomeCore:
|
||||
target.config_path = Path("foo/config")
|
||||
return target
|
||||
|
||||
def test_add_platformio_option_merges_string_into_list(self, target) -> None:
|
||||
"""A user platformio_options string lands after a component's list
|
||||
(FINAL priority) and must merge as one element, not assert."""
|
||||
target.add_platformio_option("extra_scripts", ["post:pch.py"])
|
||||
target.add_platformio_option("extra_scripts", "pre:mine.py")
|
||||
assert target.platformio_options["extra_scripts"] == [
|
||||
"post:pch.py",
|
||||
"pre:mine.py",
|
||||
]
|
||||
|
||||
def test_reset(self, target):
|
||||
"""Call reset on target and compare to new instance"""
|
||||
other = core.EsphomeCore().__dict__
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
"""The pch prefix must keep resolving; a rename would silently
|
||||
collapse the precompiled set to defines.h with strict CI still green."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome.build_helpers.pch import PCH_PREFIX_HEADER
|
||||
|
||||
REPO = Path(__file__).parents[2]
|
||||
|
||||
|
||||
def test_pch_prefix_resolves() -> None:
|
||||
prefix = REPO / PCH_PREFIX_HEADER
|
||||
assert prefix.is_file()
|
||||
body = prefix.read_text()
|
||||
includes = re.findall(r'#include "([^"]+)"', body)
|
||||
assert includes, "prefix wrapper folds nothing"
|
||||
for name in includes:
|
||||
assert (REPO / name).is_file(), f"{name} does not resolve"
|
||||
# The C guard is what keeps build_src_flags safe on C/assembly edges
|
||||
assert "#ifdef __cplusplus" in body
|
||||
Reference in New Issue
Block a user