mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 11:26:39 +00:00
[nrf52] Use the shared managed ccache settings for the native build (#20027)
This commit is contained in:
@@ -9,6 +9,7 @@ import subprocess
|
||||
|
||||
from esphome import pins
|
||||
from esphome.build_helpers import pch
|
||||
from esphome.build_helpers.ccache import resolve_ccache_path
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import (
|
||||
add_extra_script,
|
||||
@@ -584,7 +585,7 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
|
||||
raise EsphomeError("Not implemented yet")
|
||||
check_and_install()
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
env = get_build_env(None) # no compile, just nrfutil
|
||||
build_dir = CORE.relative_pioenvs_path(CORE.name)
|
||||
dfu_package = build_dir / "firmware.zip"
|
||||
if not dfu_package.is_file():
|
||||
@@ -666,7 +667,7 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
|
||||
if not CORE.using_toolchain_platformio:
|
||||
check_and_install()
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
env = get_build_env(resolve_ccache_path()) # west flash may rebuild
|
||||
build_dir = CORE.relative_pioenvs_path(CORE.name)
|
||||
west_cmd = [
|
||||
str(paths["python_executable"]),
|
||||
@@ -949,7 +950,10 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
check_and_install()
|
||||
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
# Depend mode in the shared ccache settings keeps the .gch sound
|
||||
# across Kconfig flips.
|
||||
ccache = resolve_ccache_path()
|
||||
env = get_build_env(ccache)
|
||||
|
||||
pch_on = _pch_usable()
|
||||
cmake_lists_changed = _generate_cmake_lists(pch_on)
|
||||
@@ -976,11 +980,6 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
|
||||
if pch_on:
|
||||
pch.log_pch_in_use()
|
||||
# Zephyr turns ccache on by itself when it is installed
|
||||
env.update(pch.ccache_pch_env())
|
||||
# Depend mode, or a Kconfig flip reuses a stale .gch: autoconf.h is
|
||||
# all #defines, which vanish from the preprocessed creation hash.
|
||||
env.setdefault("CCACHE_DEPEND", "1")
|
||||
_write_pch_checksum(build_dir, source_dir)
|
||||
|
||||
west_cmd = _west_build_command(
|
||||
|
||||
@@ -277,7 +277,7 @@ def generate_compile_commands(work_dir: Path, platformio_ini: Path) -> Path:
|
||||
]
|
||||
if not run_command_ok(
|
||||
west_cmd,
|
||||
env=get_build_env(),
|
||||
env=get_build_env(None), # configure only, nothing compiles
|
||||
stream_output=True,
|
||||
cwd=str(paths["framework_path"]),
|
||||
):
|
||||
|
||||
@@ -10,6 +10,7 @@ import platform
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from esphome.build_helpers.ccache import ccache_env
|
||||
from esphome.build_helpers.tools_cache import SDK_NRF_TOOLS_CACHE, tools_cache_path
|
||||
from esphome.components.zephyr.const import KEY_SYSBUILD, KEY_ZEPHYR
|
||||
import esphome.config_validation as cv
|
||||
@@ -194,7 +195,14 @@ def get_build_paths() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def get_build_env() -> dict:
|
||||
def get_build_env(ccache: str | None) -> dict:
|
||||
"""Build the west/sdk-nrf process environment.
|
||||
|
||||
``ccache`` is the resolved binary (resolve_ccache_path), or None when
|
||||
ccache is disabled or the caller never compiles; it brings the shared
|
||||
managed-ccache settings and the pch sloppiness, so every caller that
|
||||
may compile gets the same cache.
|
||||
"""
|
||||
version = _get_version_str()
|
||||
venv_bin_dir = get_python_env_executable_path(
|
||||
_get_python_env_path(version), "python"
|
||||
@@ -211,6 +219,12 @@ def get_build_env() -> dict:
|
||||
# non-root builds failed to locate the SDK with it, while
|
||||
# ZEPHYR_SDK_INSTALL_DIR fixed the same invocation.
|
||||
env["ZEPHYR_SDK_INSTALL_DIR"] = str(_get_toolchain_path(_get_toolchain_version()))
|
||||
if ccache is None:
|
||||
# Zephyr wraps compiles with any ccache it finds; unmanaged it
|
||||
# must not cache (a sysbuild image never sees USE_CCACHE=0).
|
||||
env.setdefault("CCACHE_DISABLE", "1")
|
||||
else:
|
||||
env.update(ccache_env(ccache, SDK_NRF_TOOLS_CACHE))
|
||||
return env
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
from esphome.components import nrf52
|
||||
from esphome.components.nrf52 import framework
|
||||
from esphome.components.zephyr.const import KEY_BOARD
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION, Toolchain
|
||||
@@ -105,6 +106,23 @@ def test_pch_checksum_waits_for_the_first_configure(tmp_path: Path) -> None:
|
||||
assert not build_dir.exists()
|
||||
|
||||
|
||||
def _fake_build_env(ccache: str | None) -> dict[str, str]:
|
||||
"""The real get_build_env with only the install path lookups stubbed."""
|
||||
with (
|
||||
patch.object(framework, "_get_version_str", return_value="v1"),
|
||||
patch.object(framework, "_get_python_env_path", return_value=Path("/penv")),
|
||||
patch.object(
|
||||
framework,
|
||||
"get_python_env_executable_path",
|
||||
return_value=Path("/penv/bin/python"),
|
||||
),
|
||||
patch.object(framework, "_get_framework_path", return_value=Path("/fw")),
|
||||
patch.object(framework, "_get_toolchain_version", return_value="t1"),
|
||||
patch.object(framework, "_get_toolchain_path", return_value=Path("/tc")),
|
||||
):
|
||||
return framework.get_build_env(ccache)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_cmd(tmp_path: Path) -> Mock:
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
@@ -113,6 +131,7 @@ def run_cmd(tmp_path: Path) -> Mock:
|
||||
CORE.toolchain = Toolchain.SDK_NRF
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: cv.Version(3, 2, 0)}
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(nrf52, "check_and_install"),
|
||||
patch.object(nrf52, "_generate_cmake_lists", return_value=False),
|
||||
patch.object(
|
||||
@@ -120,18 +139,18 @@ def run_cmd(tmp_path: Path) -> Mock:
|
||||
"get_build_paths",
|
||||
return_value={"python_executable": "python3", "framework_path": tmp_path},
|
||||
),
|
||||
patch.object(nrf52, "get_build_env", return_value={}),
|
||||
patch.object(nrf52, "get_build_env", side_effect=_fake_build_env),
|
||||
patch.object(nrf52, "resolve_ccache_path", return_value="/usr/bin/ccache"),
|
||||
patch.object(nrf52, "zephyr_data", return_value={KEY_BOARD: "board"}),
|
||||
patch.object(nrf52, "run_command_ok", return_value=False) as run,
|
||||
):
|
||||
yield run
|
||||
|
||||
|
||||
def test_ccache_pch_settings_reach_west(run_cmd: Mock) -> None:
|
||||
# clear=True also drops ambient CCACHE_* overrides; the header is on
|
||||
# explicitly since Windows hosts start with it off
|
||||
def test_shared_ccache_settings_reach_west(run_cmd: Mock, tmp_path: Path) -> None:
|
||||
# The header is on explicitly since Windows hosts start with it off
|
||||
with (
|
||||
patch.dict("os.environ", {"ESPHOME_PCH_ENABLE": "1"}, clear=True),
|
||||
patch.dict("os.environ", {"ESPHOME_PCH_ENABLE": "1"}),
|
||||
pytest.raises(EsphomeError, match="nRF52 native build failed"),
|
||||
):
|
||||
nrf52.run_compile(None, {})
|
||||
@@ -140,23 +159,43 @@ def test_ccache_pch_settings_reach_west(run_cmd: Mock) -> None:
|
||||
assert env["CCACHE_SLOPPINESS"] == "pch_defines,time_macros"
|
||||
# Without depend mode a Kconfig flip reuses a stale .gch
|
||||
assert env["CCACHE_DEPEND"] == "1"
|
||||
# The full managed set, not a bespoke subset
|
||||
assert env["CCACHE_DIR"].endswith("ccache")
|
||||
assert env["CCACHE_NOHASHDIR"] == "true"
|
||||
assert env["CCACHE_BASEDIR"] == str((tmp_path / "build").resolve())
|
||||
assert "CCACHE_DISABLE" not in env
|
||||
|
||||
|
||||
def test_ccache_depend_respects_a_user_override(run_cmd: Mock) -> None:
|
||||
def test_user_exported_ccache_values_win(run_cmd: Mock) -> None:
|
||||
user = {"ESPHOME_PCH_ENABLE": "1", "CCACHE_DEPEND": "0", "CCACHE_DIR": "/mine"}
|
||||
with (
|
||||
patch.object(nrf52, "get_build_env", return_value={"CCACHE_DEPEND": "0"}),
|
||||
patch.dict("os.environ", {"ESPHOME_PCH_ENABLE": "1"}, clear=True),
|
||||
patch.dict("os.environ", user),
|
||||
pytest.raises(EsphomeError, match="nRF52 native build failed"),
|
||||
):
|
||||
nrf52.run_compile(None, {})
|
||||
assert run_cmd.call_args.kwargs["env"]["CCACHE_DEPEND"] == "0"
|
||||
env = run_cmd.call_args.kwargs["env"]
|
||||
assert env["CCACHE_DEPEND"] == "0"
|
||||
assert env["CCACHE_DIR"] == "/mine"
|
||||
|
||||
|
||||
def test_disabled_leaves_the_west_environment_alone(
|
||||
run_cmd: Mock, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("ESPHOME_PCH_ENABLE", "0")
|
||||
with pytest.raises(EsphomeError, match="nRF52 native build failed"):
|
||||
def test_no_ccache_disables_the_zephyr_launcher(run_cmd: Mock) -> None:
|
||||
"""ESPHOME_CCACHE_ENABLE=0 must also stop Zephyr's self-enabled ccache."""
|
||||
with (
|
||||
patch.object(nrf52, "resolve_ccache_path", return_value=None),
|
||||
pytest.raises(EsphomeError, match="nRF52 native build failed"),
|
||||
):
|
||||
nrf52.run_compile(None, {})
|
||||
assert "CCACHE_PCH_EXTSUM" not in run_cmd.call_args.kwargs["env"]
|
||||
assert "CCACHE_DEPEND" not in run_cmd.call_args.kwargs["env"]
|
||||
env = run_cmd.call_args.kwargs["env"]
|
||||
assert env["CCACHE_DISABLE"] == "1"
|
||||
assert "CCACHE_DEPEND" not in env
|
||||
|
||||
|
||||
def test_disabled_pch_still_gets_the_shared_settings(run_cmd: Mock) -> None:
|
||||
with (
|
||||
patch.dict("os.environ", {"ESPHOME_PCH_ENABLE": "0"}),
|
||||
pytest.raises(EsphomeError, match="nRF52 native build failed"),
|
||||
):
|
||||
nrf52.run_compile(None, {})
|
||||
env = run_cmd.call_args.kwargs["env"]
|
||||
assert "CCACHE_PCH_EXTSUM" not in env
|
||||
assert env["CCACHE_DEPEND"] == "1"
|
||||
|
||||
@@ -1078,8 +1078,9 @@ def test_get_build_env(
|
||||
containerized non-root builds and was removed.
|
||||
"""
|
||||
monkeypatch.setenv("SOME_PREEXISTING_VAR", "kept")
|
||||
monkeypatch.delenv("CCACHE_DISABLE", raising=False)
|
||||
|
||||
env = get_build_env()
|
||||
env = get_build_env(None)
|
||||
|
||||
tools = get_sdk_nrf_tools_path()
|
||||
venv_bin_dir = get_python_env_executable_path(
|
||||
@@ -1096,6 +1097,30 @@ def test_get_build_env(
|
||||
assert "Zephyr-sdk_DIR" not in env
|
||||
# The rest of the process environment is inherited
|
||||
assert env["SOME_PREEXISTING_VAR"] == "kept"
|
||||
# No managed settings without a resolved binary; the self-enabled
|
||||
# Zephyr ccache must not cache
|
||||
assert "CCACHE_DIR" not in env or "CCACHE_DIR" in os.environ
|
||||
assert env["CCACHE_DISABLE"] == "1"
|
||||
|
||||
|
||||
def test_get_build_env_with_ccache(
|
||||
nrf52_dirs: SimpleNamespace, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""A resolved ccache brings the shared managed settings."""
|
||||
for key in (
|
||||
"CCACHE_DIR",
|
||||
"CCACHE_DEPEND",
|
||||
"CCACHE_NOHASHDIR",
|
||||
"CCACHE_BASEDIR",
|
||||
"CCACHE_DISABLE",
|
||||
):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
CORE.build_path = tmp_path / "build"
|
||||
env = get_build_env("/usr/bin/ccache")
|
||||
assert env["CCACHE_DIR"] == str(get_sdk_nrf_tools_path() / "ccache")
|
||||
assert env["CCACHE_DEPEND"] == "1"
|
||||
assert env["CCACHE_BASEDIR"] == str((tmp_path / "build").resolve())
|
||||
assert "CCACHE_DISABLE" not in env
|
||||
|
||||
|
||||
def test_get_build_env_sdk_3_4_0_uses_toolchain_root(
|
||||
@@ -1104,7 +1129,7 @@ def test_get_build_env_sdk_3_4_0_uses_toolchain_root(
|
||||
"""For NCS >= 3.4.0, ZEPHYR_SDK_INSTALL_DIR still points at the toolchain root."""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse("3.4.0")}
|
||||
|
||||
env = get_build_env()
|
||||
env = get_build_env(None)
|
||||
|
||||
tools = get_sdk_nrf_tools_path()
|
||||
assert env["ZEPHYR_SDK_INSTALL_DIR"] == str(tools / "toolchains" / "1.0.1")
|
||||
|
||||
Reference in New Issue
Block a user