[host] Build natively with ninja instead of PlatformIO (#19214)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Jesse Hills
2026-09-28 11:29:59 +02:00
committed by GitHub
co-authored by J. Nick Koston
parent bb7af14012
commit 32e11c4f4a
47 changed files with 2532 additions and 630 deletions
+10 -11
View File
@@ -22,8 +22,8 @@ from esphome.__main__ import command_compile, parse_args
from esphome.config import validate_config
from esphome.const import CONF_PLATFORM
from esphome.core import CORE
from esphome.host.toolchain import get_elf_path
from esphome.loader import get_component, get_platform
from esphome.platformio.toolchain import get_idedata
from tests.testing_helpers import ComponentManifestOverride, set_testing_manifest
# This must coincide with the version in /platformio.ini
@@ -220,7 +220,7 @@ def create_host_config(
friendly_name: str,
libraries: str | list[str],
includes: list[str],
platformio_options: dict,
build_flags: list[str],
) -> dict:
"""Create an ESPHome host configuration for C++ builds.
@@ -229,7 +229,7 @@ def create_host_config(
friendly_name: Human-readable name
libraries: PlatformIO library specification(s)
includes: List of include folders for the build
platformio_options: Dict of platformio_options to set
build_flags: Compiler/linker flags for the build
Returns:
Configuration dict for ESPHome
@@ -239,7 +239,7 @@ def create_host_config(
"name": config_name,
"friendly_name": friendly_name,
"libraries": libraries,
"platformio_options": platformio_options,
"build_flags": build_flags,
"includes": includes,
},
HOST_KEY: {},
@@ -403,13 +403,12 @@ def compile_and_get_binary(
return EXIT_COMPILE_ERROR, None
# After a successful compilation, locate the executable:
idedata = get_idedata(config)
if idedata is None:
program_path = get_elf_path()
if not program_path.is_file():
print("Cannot find executable")
return EXIT_NO_EXECUTABLE, None
program_path: str = idedata.raw["prog_path"]
return EXIT_OK, program_path
return EXIT_OK, str(program_path)
def build_and_run(
@@ -419,7 +418,7 @@ def build_and_run(
config_prefix: str,
friendly_name: str,
libraries: str | list[str],
platformio_options: dict,
build_flags: list[str],
main_entry: str,
label: str = "build",
build_only: bool = False,
@@ -438,7 +437,7 @@ def build_and_run(
config_prefix: Prefix for the config name (e.g. "cpptests", "cppbench")
friendly_name: Human-readable name for the config
libraries: PlatformIO library specification(s)
platformio_options: PlatformIO options dict
build_flags: Compiler/linker flags for the build
main_entry: Name of the main entry file (e.g. "main.cpp")
label: Label for log messages
build_only: If True, print binary path and return without running
@@ -488,7 +487,7 @@ def build_and_run(
config_name: str = f"{config_prefix}-" + hash_components(components)
config = create_host_config(
config_name, friendly_name, libraries, includes, platformio_options
config_name, friendly_name, libraries, includes, build_flags
)
exit_code, program_path = compile_and_get_binary(
+11 -19
View File
@@ -25,19 +25,14 @@ CORE_BENCHMARKS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "core"
# allow benchmarks to compile on the host platform.
STUBS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "stubs"
PLATFORMIO_OPTIONS = {
"build_flags": [
"-Os", # match firmware optimization level (detects inlining regressions)
"-g", # debug symbols for profiling
"-ffunction-sections", # required for dead-code stripping with -Os
"-fdata-sections", # required for dead-code stripping with -Os
"-DUSE_BENCHMARK", # disable WarnIfComponentBlockingGuard in finish()
f"-I{STUBS_DIR}", # stub headers for ESP32-only components
],
# Use deep+ LDF mode to ensure PlatformIO detects the benchmark
# library dependency from nested includes.
"lib_ldf_mode": "deep+",
}
BUILD_FLAGS = [
"-Os", # match firmware optimization level (detects inlining regressions)
"-g", # debug symbols for profiling
"-ffunction-sections", # required for dead-code stripping with -Os
"-fdata-sections", # required for dead-code stripping with -Os
"-DUSE_BENCHMARK", # disable WarnIfComponentBlockingGuard in finish()
f"-I{STUBS_DIR}", # stub headers for ESP32-only components
]
def run_benchmarks(selected_components: list[str], build_only: bool = False) -> int:
@@ -46,7 +41,7 @@ def run_benchmarks(selected_components: list[str], build_only: bool = False) ->
# containing {"lib_path": "/path/to/google_benchmark"}.
lib_config_json = os.environ.get("BENCHMARK_LIB_CONFIG")
pio_options = PLATFORMIO_OPTIONS
build_flags = BUILD_FLAGS
if lib_config_json:
lib_config = json.loads(lib_config_json)
benchmark_lib = f"benchmark=symlink://{lib_config['lib_path']}"
@@ -61,10 +56,7 @@ def run_benchmarks(selected_components: list[str], build_only: bool = False) ->
"-DCODSPEED_ANALYSIS",
f'-DCODSPEED_ROOT_DIR=\\"{project_root}\\"',
]
pio_options = {
**PLATFORMIO_OPTIONS,
"build_flags": PLATFORMIO_OPTIONS["build_flags"] + codspeed_flags,
}
build_flags = BUILD_FLAGS + codspeed_flags
else:
benchmark_lib = PLATFORMIO_GOOGLE_BENCHMARK_LIB
@@ -77,7 +69,7 @@ def run_benchmarks(selected_components: list[str], build_only: bool = False) ->
config_prefix="cppbench",
friendly_name="CPP Benchmarks",
libraries=benchmark_lib,
platformio_options=pio_options,
build_flags=build_flags,
main_entry="main.cpp",
label="benchmarks",
build_only=build_only,
+11 -19
View File
@@ -15,24 +15,16 @@ from helpers import get_all_components, root_path
# Path to /tests/components
COMPONENTS_TESTS_DIR: Path = Path(root_path) / "tests" / "components"
PLATFORMIO_OPTIONS = {
"build_type": "debug",
"build_unflags": [
"-Os", # remove size-opt flag
],
"build_flags": [
"-Og", # optimize for debug
"-DESPHOME_DEBUG", # enable debug assertions
# Enable the address and undefined behavior sanitizers
"-fsanitize=address",
"-fsanitize=undefined",
"-fno-omit-frame-pointer",
],
"debug_build_flags": [ # only for debug builds
"-g3", # max debug info
"-ggdb3",
],
}
BUILD_FLAGS = [
"-Og", # optimize for debug
"-g3", # max debug info
"-ggdb3",
"-DESPHOME_DEBUG", # enable debug assertions
# Enable the address and undefined behavior sanitizers
"-fsanitize=address",
"-fsanitize=undefined",
"-fno-omit-frame-pointer",
]
def run_tests(selected_components: list[str]) -> int:
@@ -47,7 +39,7 @@ def run_tests(selected_components: list[str]) -> int:
config_prefix="cpptests",
friendly_name="CPP Unit Tests",
libraries=PLATFORMIO_GOOGLE_TEST_LIB,
platformio_options=PLATFORMIO_OPTIONS,
build_flags=BUILD_FLAGS,
main_entry="main.cpp",
label="unit tests",
)
+16 -5
View File
@@ -111,14 +111,25 @@ INTEGRATION_TESTS_SPLIT_THRESHOLD = 10
INTEGRATION_TESTS_SPLIT_BUCKETS = 5
INTEGRATION_TESTS_TARGET_BUCKET_WEIGHT = 360.0
# platformio and aioesphomeapi (requirements.txt), the pytest stack
# (requirements_test.txt) and the fixture every session compiles; a change
# to any runs the full matrix
# aioesphomeapi (requirements.txt), the pytest stack (requirements_test.txt)
# and the native host build backend every test compiles with; a change to
# any runs the full matrix
INTEGRATION_TESTS_TRIGGER_FILES = frozenset(
{
"requirements.txt",
"requirements_test.txt",
"tests/integration/fixtures/cache_init.yaml",
"esphome/arduino/library.py",
"esphome/build_gen/build_tool.py",
"esphome/build_gen/host.py",
"esphome/build_helpers/ccache.py",
"esphome/build_helpers/idedata.py",
"esphome/build_helpers/native.py",
"esphome/build_helpers/ninja.py",
"esphome/build_helpers/ninja_gen.py",
"esphome/build_helpers/tools_cache.py",
"esphome/framework_helpers.py",
"esphome/host/toolchain.py",
"esphome/platformio/library.py",
}
)
@@ -240,7 +251,7 @@ def determine_integration_tests(branch: str | None = None) -> tuple[bool, list[s
- conftest.py, types.py, const.py, entity_utils.py, state_utils.py, etc.
4. A file in INTEGRATION_TESTS_TRIGGER_FILES changed
- The dependency pins and the session init fixture affect every test
- The dependency pins and the host build backend affect every test
Returns (run_all=False, [test_files...]) when: