mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
Merge branch 'dev' into jesserockz-2026-503
This commit is contained in:
@@ -52,6 +52,7 @@ COMMON_BUS_PATH = (
|
||||
# the packages on the right as well
|
||||
PACKAGE_DEPENDENCIES = {
|
||||
"modbus": ["uart"], # modbus packages include uart packages
|
||||
"modbus_server": ["uart"], # modbus_server packages include uart packages
|
||||
# Add more package dependencies here as needed
|
||||
}
|
||||
|
||||
|
||||
@@ -2401,7 +2401,10 @@ def get_varint64_ifdef(
|
||||
# At least one 64-bit varint field is unconditional, so the guard must be unconditional.
|
||||
return True, None
|
||||
ifdefs.discard(None)
|
||||
return True, ifdefs.pop() if len(ifdefs) == 1 else None
|
||||
# Several guards: the define is needed under any of them, so emit the union.
|
||||
# Falling back to unconditional would pull 64-bit varint support into builds
|
||||
# that have none of them.
|
||||
return True, " || ".join(sorted(ifdefs))
|
||||
|
||||
|
||||
def build_enum_type(desc, enum_ifdef_map) -> tuple[str, str, str]:
|
||||
|
||||
@@ -1134,13 +1134,29 @@ def convert_keys(converted, schema, path):
|
||||
else:
|
||||
converted["key"] = "String"
|
||||
key_string_match = re.search(
|
||||
r"<function (\w*) at \w*>", str(k), re.IGNORECASE
|
||||
r"<function ([^ ]+) at \w+>", str(k), re.IGNORECASE
|
||||
)
|
||||
if key_string_match:
|
||||
converted["key_type"] = key_string_match.group(1)
|
||||
else:
|
||||
converted["key_type"] = str(k)
|
||||
|
||||
# A marker-wrapped callable key (e.g. script.execute's
|
||||
# ``cv.Optional(validate_parameter_name)``) is a wildcard matcher;
|
||||
# ``str(marker)`` is the function repr, whose heap address would
|
||||
# churn the dump every build. Normalize like the bare-callable
|
||||
# branch above: record the validator name in ``key_type`` and file
|
||||
# the config var under ``string``.
|
||||
key_name = str(k)
|
||||
if isinstance(k, vol.Marker) and callable(k.schema):
|
||||
key_string_match = re.search(
|
||||
r"<function ([^ ]+) at \w+>", key_name, re.IGNORECASE
|
||||
)
|
||||
result["key_type"] = (
|
||||
key_string_match.group(1) if key_string_match else key_name
|
||||
)
|
||||
key_name = "string"
|
||||
|
||||
# ``cv.OnlyWith`` / ``cv.OnlyWithout`` expose ``default`` as
|
||||
# a property that returns ``vol.UNDEFINED`` when the gating
|
||||
# component isn't loaded — and at schema-generation time
|
||||
@@ -1220,7 +1236,7 @@ def convert_keys(converted, schema, path):
|
||||
for base_k, base_v in get_overridden_config(k, converted).items():
|
||||
if base_k in result and base_v == result[base_k]:
|
||||
result.pop(base_k)
|
||||
converted["schema"][S_CONFIG_VARS][str(k)] = result
|
||||
converted["schema"][S_CONFIG_VARS][key_name] = result
|
||||
if "key" in converted and converted["key"] == "String":
|
||||
config_vars = converted["schema"]["config_vars"]
|
||||
assert len(config_vars) == 1
|
||||
|
||||
+1
-1
@@ -560,7 +560,7 @@ def lint_constants_usage():
|
||||
# Maximum allowed CONF_ constants in esphome/const.py.
|
||||
# This file is frozen — new constants go in esphome/components/const/__init__.py.
|
||||
# Decrease this number when constants are moved out of const.py.
|
||||
CONST_PY_MAX_CONF = 1015
|
||||
CONST_PY_MAX_CONF = 1017
|
||||
|
||||
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
|
||||
@@ -5,11 +5,13 @@ import os
|
||||
from pathlib import Path
|
||||
import queue
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
import colorama
|
||||
@@ -29,6 +31,36 @@ from helpers import (
|
||||
)
|
||||
|
||||
|
||||
def gcc_multilib_directory(idedata: dict[str, Any]) -> str | None:
|
||||
"""The toolchain's active multilib subdirectory (e.g. "thumb"), if any.
|
||||
|
||||
PlatformIO's idedata lists the generic toolchain include directories; GCC
|
||||
resolves the active multilib subdirectory internally while searching them.
|
||||
Toolchains without a default multilib (pico-quick-toolchain 5.0.0+) ship
|
||||
the libstdc++ target config (bits/c++config.h) only inside the multilib
|
||||
subdirectories, so clang needs the resolved directory spelled out.
|
||||
"""
|
||||
machine_flags = [f for f in idedata["cxx_flags"] if f.startswith("-m")]
|
||||
cmd = [idedata["cxx_path"], *machine_flags, "-print-multi-directory"]
|
||||
try:
|
||||
multilib = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
except (OSError, subprocess.CalledProcessError) as err:
|
||||
# Without the multilib dir, toolchains lacking a default multilib fail
|
||||
# later with "bits/c++config.h not found"; point at the probe instead.
|
||||
stderr = getattr(err, "stderr", "") or ""
|
||||
print(
|
||||
f"WARNING: multilib probe failed ({shlex.join(cmd)}): {err} {stderr}".strip(),
|
||||
file=sys.stderr,
|
||||
)
|
||||
return None
|
||||
return None if multilib in ("", ".") else multilib
|
||||
|
||||
|
||||
def clang_options(idedata, environment):
|
||||
cmd = []
|
||||
|
||||
@@ -203,9 +235,12 @@ def clang_options(idedata, environment):
|
||||
# toolchain include directories, using -isystem to suppress their errors
|
||||
# idedata contains include directories for all toolchains of this platform, only use those from the one in use
|
||||
toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../")
|
||||
multilib = gcc_multilib_directory(idedata)
|
||||
toolchain_includes = []
|
||||
for directory in idedata["includes"]["toolchain"]:
|
||||
if directory.startswith(toolchain_dir) and "picolibc" not in directory:
|
||||
if multilib and (multilib_dir := Path(directory) / multilib).is_dir():
|
||||
toolchain_includes.extend(["-isystem", str(multilib_dir)])
|
||||
toolchain_includes.extend(["-isystem", directory])
|
||||
|
||||
# library include directories, using -isystem to suppress their errors
|
||||
|
||||
@@ -18,6 +18,7 @@ from pathlib import Path
|
||||
# Root-relative paths whose contents affect clang-tidy results.
|
||||
CLANG_TIDY_GLOBAL_FILES = (
|
||||
".clang-tidy",
|
||||
"script/clang-tidy",
|
||||
"platformio.ini",
|
||||
"requirements_dev.txt",
|
||||
"esphome/idf_component.yml",
|
||||
|
||||
@@ -23,7 +23,7 @@ what files have changed. It outputs JSON with the following structure:
|
||||
}
|
||||
|
||||
The CI workflow uses this information to:
|
||||
- Gate the unconditional jobs (ci-custom, pytest, pre-commit-ci-lite) via core_ci;
|
||||
- Gate the unconditional jobs (ci-custom, pytest, lint-format) via core_ci;
|
||||
false when a pull_request only touches CI-irrelevant meta paths (other workflow
|
||||
files, .github/actions/build-image/*, .yamllint, .github/dependabot.yml, docker/**)
|
||||
so workflow-only PRs satisfy the required CI Status check without running the
|
||||
@@ -63,6 +63,7 @@ from helpers import (
|
||||
CPP_FILE_EXTENSIONS,
|
||||
ESPHOME_TESTS_COMPONENTS_PATH,
|
||||
PYTHON_FILE_EXTENSIONS,
|
||||
base_python_changed,
|
||||
changed_files,
|
||||
core_changed,
|
||||
filter_component_and_test_cpp_files,
|
||||
@@ -657,16 +658,20 @@ BENCHMARK_INFRASTRUCTURE_FILES = frozenset(
|
||||
|
||||
|
||||
def should_run_benchmarks(branch: str | None = None) -> bool:
|
||||
"""Determine if C++ benchmarks should run based on changed files.
|
||||
"""Determine if benchmarks (C++ and Python) should run based on changed files.
|
||||
|
||||
Benchmarks run when any of the following conditions are met:
|
||||
|
||||
1. Core C++ files changed (esphome/core/*)
|
||||
2. The host platform changed (esphome/components/host/*) — benchmarks
|
||||
1. Core files changed (esphome/core/*, C++ or Python)
|
||||
2. Top-level Python files changed (esphome/*.py and esphome/*.pyi) —
|
||||
the Python benchmarks exercise config loading (config.py,
|
||||
yaml_util.py, ...), so a slowdown there is invisible unless the
|
||||
benchmarks job runs
|
||||
3. The host platform changed (esphome/components/host/*) — benchmarks
|
||||
are built and run on the host platform, so its implementations of
|
||||
``millis()``/``micros()``/etc. affect every benchmark
|
||||
3. A directly changed component has benchmark files (no dependency expansion)
|
||||
4. Benchmark infrastructure changed (tests/benchmarks/*, script/cpp_benchmark.py,
|
||||
4. A directly changed component has benchmark files (no dependency expansion)
|
||||
5. Benchmark infrastructure changed (tests/benchmarks/*, script/cpp_benchmark.py,
|
||||
script/build_helpers.py, script/setup_codspeed_lib.py)
|
||||
|
||||
Unlike unit tests, benchmarks do NOT expand to dependent components.
|
||||
@@ -683,6 +688,11 @@ def should_run_benchmarks(branch: str | None = None) -> bool:
|
||||
if core_changed(files):
|
||||
return True
|
||||
|
||||
# Top-level esphome/*.py modules are what the Python benchmarks in
|
||||
# tests/benchmarks/python/ exercise
|
||||
if base_python_changed(files):
|
||||
return True
|
||||
|
||||
# Host platform supplies the runtime that benchmarks execute on
|
||||
if any(f.startswith("esphome/components/host/") for f in files):
|
||||
return True
|
||||
@@ -708,7 +718,7 @@ def should_run_benchmarks(branch: str | None = None) -> bool:
|
||||
|
||||
|
||||
# Files / path patterns whose changes alone don't warrant running the
|
||||
# unconditional CI jobs (`ci-custom`, `pytest`, `pre-commit-ci-lite`).
|
||||
# unconditional CI jobs (`ci-custom`, `pytest`, `lint-format`).
|
||||
# Single source of truth for what we treat as "CI-irrelevant" on
|
||||
# pull_request events; ci.yml used to encode this in its own
|
||||
# `pull_request.paths` filter, but that hid the required `CI Status`
|
||||
@@ -752,7 +762,7 @@ def _is_ci_irrelevant_path(path: str) -> bool:
|
||||
|
||||
|
||||
def should_run_core_ci(branch: str | None = None) -> bool:
|
||||
"""Determine if the unconditional CI jobs (ci-custom/pytest/pre-commit-ci-lite) should run.
|
||||
"""Determine if the unconditional CI jobs (ci-custom/pytest/lint-format) should run.
|
||||
|
||||
Returns False only when every changed file is in the CI-irrelevant set
|
||||
above (see ``_is_ci_irrelevant_path``). Empty diffs return True so we
|
||||
@@ -1177,7 +1187,7 @@ def main() -> None:
|
||||
|
||||
# Determine what should run
|
||||
# core_ci gates the unconditional jobs in ci.yml (ci-custom, pytest,
|
||||
# pre-commit-ci-lite). Non-pull_request events (push to dev/beta/release
|
||||
# lint-format). Non-pull_request events (push to dev/beta/release
|
||||
# and merge_group) always run them so behavior like venv-cache saves on
|
||||
# push to dev is preserved.
|
||||
event_name = os.environ.get("GITHUB_EVENT_NAME", "")
|
||||
|
||||
@@ -1380,6 +1380,27 @@ def core_changed(files: list[str]) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def base_python_changed(files: list[str]) -> bool:
|
||||
"""Check if any Python file directly in esphome/ has changed.
|
||||
|
||||
Matches top-level modules and stubs (.py and .pyi) like esphome/config.py
|
||||
and esphome/yaml_util.py but not files in subdirectories such as
|
||||
esphome/components/ or esphome/dashboard/.
|
||||
|
||||
Args:
|
||||
files: List of file paths to check
|
||||
|
||||
Returns:
|
||||
True if any top-level esphome Python file has changed
|
||||
"""
|
||||
return any(
|
||||
f.startswith("esphome/")
|
||||
and f.endswith(PYTHON_FILE_EXTENSIONS)
|
||||
and "/" not in f.removeprefix("esphome/")
|
||||
for f in files
|
||||
)
|
||||
|
||||
|
||||
def get_cpp_changed_components(files: list[str]) -> list[str]:
|
||||
"""Get components that have changed C++ files or tests.
|
||||
|
||||
|
||||
+5
-1
@@ -25,7 +25,11 @@ fi
|
||||
uv pip install setuptools wheel
|
||||
uv pip install -e ".[dev,test]" --config-settings editable_mode=compat
|
||||
|
||||
pre-commit install
|
||||
# --overwrite replaces any hook already in place. Without it, prek finds a
|
||||
# previously installed pre-commit hook, moves it aside to
|
||||
# .git/hooks/pre-commit.legacy and keeps calling it, so every commit would
|
||||
# run both tools.
|
||||
prek install --overwrite
|
||||
|
||||
mkdir -p .temp
|
||||
|
||||
|
||||
+5
-1
@@ -17,7 +17,11 @@ pip3 install -r requirements.txt -r requirements_test.txt -r requirements_dev.tx
|
||||
pip3 install setuptools wheel
|
||||
pip3 install -e ".[dev,test]" --config-settings editable_mode=compat
|
||||
|
||||
pre-commit install
|
||||
rem --overwrite replaces any hook already in place. Without it, prek finds a
|
||||
rem previously installed pre-commit hook, moves it aside to
|
||||
rem .git/hooks/pre-commit.legacy and keeps calling it, so every commit would
|
||||
rem run both tools.
|
||||
prek install --overwrite
|
||||
|
||||
echo .
|
||||
echo .
|
||||
|
||||
Reference in New Issue
Block a user