From 7fbb647c65aefef4f1a1e9d09ca2fa55fe76bb70 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:59:33 -0400 Subject: [PATCH] [espidf] Honor compile_process_limit in native ESP-IDF builds (#17857) --- esphome/espidf/toolchain.py | 16 ++++-- tests/unit_tests/test_espidf_toolchain.py | 59 ++++++++++++++++++++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/esphome/espidf/toolchain.py b/esphome/espidf/toolchain.py index f2bb99d970..9dd3474910 100644 --- a/esphome/espidf/toolchain.py +++ b/esphome/espidf/toolchain.py @@ -10,7 +10,12 @@ import shutil import subprocess from esphome.components.esp32.const import KEY_ESP32, KEY_FLASH_SIZE, KEY_IDF_VERSION -from esphome.const import CONF_FRAMEWORK, CONF_SOURCE +from esphome.const import ( + CONF_COMPILE_PROCESS_LIMIT, + CONF_ESPHOME, + CONF_FRAMEWORK, + CONF_SOURCE, +) from esphome.core import CORE, EsphomeError from esphome.espidf.framework import check_esp_idf_install, get_framework_env from esphome.espidf.size_summary import print_summary @@ -155,7 +160,10 @@ def _get_idf_tool(name: str) -> str: def run_idf_py( - *args, cwd: Path | None = None, capture_output: bool = False + *args, + cwd: Path | None = None, + capture_output: bool = False, + jobs: int | None = None, ) -> int | str: """Run idf.py with the given arguments.""" idf_path = _get_idf_path() @@ -163,6 +171,8 @@ def run_idf_py( raise EsphomeError("ESP-IDF not found") env = _get_idf_env() + if jobs is not None: + env = {**env, "IDF_PY_BUILD_JOBS": str(jobs)} python_executable = _get_idf_tool("python") idf_py = idf_path / "tools" / "idf.py" # Dispatch idf.py through esphome.espidf.runner, which wraps @@ -392,7 +402,7 @@ def run_compile(config, verbose: bool) -> int: args.append("build") args.append("size") - rc = run_idf_py(*args) + rc = run_idf_py(*args, jobs=config[CONF_ESPHOME].get(CONF_COMPILE_PROCESS_LIMIT)) if rc == 0: size_json = CORE.relative_build_path("build", "esp_idf_size.json") partitions = CORE.relative_build_path("partitions.csv") diff --git a/tests/unit_tests/test_espidf_toolchain.py b/tests/unit_tests/test_espidf_toolchain.py index 8731884ed3..2735746264 100644 --- a/tests/unit_tests/test_espidf_toolchain.py +++ b/tests/unit_tests/test_espidf_toolchain.py @@ -10,7 +10,12 @@ from unittest.mock import patch import pytest -from esphome.const import CONF_FRAMEWORK, CONF_SOURCE +from esphome.const import ( + CONF_COMPILE_PROCESS_LIMIT, + CONF_ESPHOME, + CONF_FRAMEWORK, + CONF_SOURCE, +) from esphome.core import CORE, EsphomeError from esphome.espidf import toolchain @@ -309,6 +314,58 @@ def test_get_cmake_output_missing_build_does_not_resolve_idf_env( mock_run.assert_not_called() +def test_run_idf_py_jobs_sets_build_jobs_env(setup_core: Path) -> None: + """The jobs argument is exported to idf.py as IDF_PY_BUILD_JOBS.""" + _setup_build(setup_core) + + with ( + patch.object(toolchain, "_get_idf_path", return_value=Path("/idf")), + patch.object(toolchain, "_get_idf_env", return_value={"PATH": "/bin"}), + patch.object(toolchain, "_get_idf_tool", return_value="python"), + patch.object(toolchain.subprocess, "run") as mock_run, + ): + mock_run.return_value.returncode = 0 + + toolchain.run_idf_py("build", jobs=2) + env = mock_run.call_args.kwargs["env"] + assert env["IDF_PY_BUILD_JOBS"] == "2" + assert env["PATH"] == "/bin" + + toolchain.run_idf_py("build") + env = mock_run.call_args.kwargs["env"] + assert "IDF_PY_BUILD_JOBS" not in env + + +def test_run_compile_passes_compile_process_limit(setup_core: Path) -> None: + """compile_process_limit is forwarded to run_idf_py as the job limit.""" + _setup_build(setup_core) + config = {CONF_ESPHOME: {CONF_COMPILE_PROCESS_LIMIT: 1}} + + with ( + patch.object(toolchain, "need_reconfigure", return_value=False), + patch.object(toolchain, "run_idf_py", return_value=0) as mock_run, + patch.object(toolchain, "print_summary"), + ): + assert toolchain.run_compile(config, verbose=False) == 0 + + mock_run.assert_called_once_with("build", "size", jobs=1) + + +def test_run_compile_without_compile_process_limit(setup_core: Path) -> None: + """When no compile_process_limit is set, no job limit is passed to idf.py.""" + _setup_build(setup_core) + config = {CONF_ESPHOME: {}} + + with ( + patch.object(toolchain, "need_reconfigure", return_value=False), + patch.object(toolchain, "run_idf_py", return_value=0) as mock_run, + patch.object(toolchain, "print_summary"), + ): + assert toolchain.run_compile(config, verbose=False) == 0 + + mock_run.assert_called_once_with("build", "size", jobs=None) + + def test_get_core_framework_version_from_core_data(): """The version is read from CORE.data when validation populated it.""" from esphome.components.esp32.const import KEY_ESP32, KEY_IDF_VERSION