mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:17:23 +00:00
[core] Stop parent git repos from breaking ESP-IDF/PlatformIO builds (#16994)
This commit is contained in:
@@ -14,6 +14,7 @@ from esphome.const import CONF_FRAMEWORK, CONF_SOURCE
|
|||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
from esphome.espidf.framework import check_esp_idf_install, get_framework_env
|
from esphome.espidf.framework import check_esp_idf_install, get_framework_env
|
||||||
from esphome.espidf.size_summary import print_summary
|
from esphome.espidf.size_summary import print_summary
|
||||||
|
from esphome.helpers import add_git_ceiling_directory
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -82,6 +83,11 @@ def _get_idf_env(version: str | None = None) -> dict[str, str]:
|
|||||||
env_cache[version] |= get_framework_env(
|
env_cache[version] |= get_framework_env(
|
||||||
*_get_esphome_esp_idf_paths(version)
|
*_get_esphome_esp_idf_paths(version)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Cap git's repo search at the config directory so ESP-IDF's
|
||||||
|
# `git describe` for the app version can't error out on an
|
||||||
|
# uninitialized or corrupt git repo in a parent directory.
|
||||||
|
add_git_ceiling_directory(env_cache[version], CORE.config_dir)
|
||||||
return env_cache[version]
|
return env_cache[version]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import MutableMapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
@@ -374,6 +375,26 @@ def is_ha_addon():
|
|||||||
return get_bool_env("ESPHOME_IS_HA_ADDON")
|
return get_bool_env("ESPHOME_IS_HA_ADDON")
|
||||||
|
|
||||||
|
|
||||||
|
def add_git_ceiling_directory(env: MutableMapping[str, str], directory: Path) -> None:
|
||||||
|
"""Add ``directory`` to ``env``'s ``GIT_CEILING_DIRECTORIES`` list.
|
||||||
|
|
||||||
|
Git stops walking up the directory tree to find a repository once it reaches
|
||||||
|
a ceiling directory, so this caps the search at ``directory`` (the ESPHome
|
||||||
|
project root). Without it, an uninitialized or corrupt git repo in a parent
|
||||||
|
directory makes the ``git describe`` that build toolchains run for the app
|
||||||
|
version error out and fail the whole build.
|
||||||
|
|
||||||
|
``GIT_CEILING_DIRECTORIES`` is an ``os.pathsep``-joined list of absolute
|
||||||
|
paths; any existing entries are preserved and duplicates are skipped.
|
||||||
|
"""
|
||||||
|
ceiling = str(directory)
|
||||||
|
existing = env.get("GIT_CEILING_DIRECTORIES", "")
|
||||||
|
parts = existing.split(os.pathsep) if existing else []
|
||||||
|
if ceiling not in parts:
|
||||||
|
parts.append(ceiling)
|
||||||
|
env["GIT_CEILING_DIRECTORIES"] = os.pathsep.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def rmtree(path: Path | str) -> None:
|
def rmtree(path: Path | str) -> None:
|
||||||
"""Remove a directory tree, handling read-only files on Windows.
|
"""Remove a directory tree, handling read-only files on Windows.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import sys
|
|||||||
|
|
||||||
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
|
from esphome.helpers import add_git_ceiling_directory
|
||||||
from esphome.util import FlashImage, run_external_process
|
from esphome.util import FlashImage, run_external_process
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -53,6 +54,10 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
|
|||||||
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
||||||
# Increase uv retry count to handle transient network errors (default is 3)
|
# Increase uv retry count to handle transient network errors (default is 3)
|
||||||
os.environ.setdefault("UV_HTTP_RETRIES", "10")
|
os.environ.setdefault("UV_HTTP_RETRIES", "10")
|
||||||
|
# Cap git's repo search at the config directory so the framework's build
|
||||||
|
# scripts running `git describe` for the app version can't error out on an
|
||||||
|
# uninitialized or corrupt git repo in a parent directory.
|
||||||
|
add_git_ceiling_directory(os.environ, CORE.config_dir)
|
||||||
# Strip the Windows extended-length path prefix from sys.executable so it
|
# Strip the Windows extended-length path prefix from sys.executable so it
|
||||||
# doesn't propagate into PlatformIO's $PYTHONEXE and break SCons-emitted
|
# doesn't propagate into PlatformIO's $PYTHONEXE and break SCons-emitted
|
||||||
# command lines run through cmd.exe.
|
# command lines run through cmd.exe.
|
||||||
|
|||||||
@@ -150,6 +150,20 @@ def test_get_idedata_regenerates_on_corrupted_cache(setup_core: Path) -> None:
|
|||||||
assert result == {"cxx_path": "regen"}
|
assert result == {"cxx_path": "regen"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_idf_env_sets_git_ceiling_directories(setup_core: Path) -> None:
|
||||||
|
"""The IDF env caps git's upward search at the config directory.
|
||||||
|
|
||||||
|
This stops ESP-IDF's `git describe` from walking into an uninitialized or
|
||||||
|
corrupt git repo in a parent directory and failing the build.
|
||||||
|
"""
|
||||||
|
toolchain._cache().env.clear()
|
||||||
|
# Set IDF_PATH so the framework-install branch is skipped.
|
||||||
|
with patch.dict(os.environ, {"IDF_PATH": str(setup_core)}):
|
||||||
|
env = toolchain._get_idf_env(version="5.5.4")
|
||||||
|
assert CORE.config_dir == setup_core
|
||||||
|
assert str(CORE.config_dir) in env["GIT_CEILING_DIRECTORIES"].split(os.pathsep)
|
||||||
|
|
||||||
|
|
||||||
def test_get_core_framework_version_from_core_data():
|
def test_get_core_framework_version_from_core_data():
|
||||||
"""The version is read from CORE.data when validation populated it."""
|
"""The version is read from CORE.data when validation populated it."""
|
||||||
from esphome.components.esp32.const import KEY_ESP32, KEY_IDF_VERSION
|
from esphome.components.esp32.const import KEY_ESP32, KEY_IDF_VERSION
|
||||||
|
|||||||
@@ -196,6 +196,33 @@ def test_is_ha_addon(monkeypatch, value, expected):
|
|||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_git_ceiling_directory_sets_when_unset():
|
||||||
|
"""An empty env gets GIT_CEILING_DIRECTORIES set to the directory."""
|
||||||
|
env: dict[str, str] = {}
|
||||||
|
directory = Path("/home/user/config")
|
||||||
|
helpers.add_git_ceiling_directory(env, directory)
|
||||||
|
assert env["GIT_CEILING_DIRECTORIES"] == str(directory)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_git_ceiling_directory_appends_to_existing():
|
||||||
|
"""An existing value is preserved and the new directory is appended."""
|
||||||
|
env = {"GIT_CEILING_DIRECTORIES": str(Path("/some/ceiling"))}
|
||||||
|
directory = Path("/home/user/config")
|
||||||
|
helpers.add_git_ceiling_directory(env, directory)
|
||||||
|
assert env["GIT_CEILING_DIRECTORIES"].split(os.pathsep) == [
|
||||||
|
str(Path("/some/ceiling")),
|
||||||
|
str(directory),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_git_ceiling_directory_skips_duplicate():
|
||||||
|
"""A directory already in the list is not appended again."""
|
||||||
|
directory = Path("/home/user/config")
|
||||||
|
env = {"GIT_CEILING_DIRECTORIES": str(directory)}
|
||||||
|
helpers.add_git_ceiling_directory(env, directory)
|
||||||
|
assert env["GIT_CEILING_DIRECTORIES"] == str(directory)
|
||||||
|
|
||||||
|
|
||||||
def test_walk_files(fixture_path):
|
def test_walk_files(fixture_path):
|
||||||
path = fixture_path / "helpers"
|
path = fixture_path / "helpers"
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,11 @@ def test_run_platformio_cli_sets_environment_variables(
|
|||||||
)
|
)
|
||||||
assert "PLATFORMIO_LIBDEPS_DIR" in os.environ
|
assert "PLATFORMIO_LIBDEPS_DIR" in os.environ
|
||||||
assert "PYTHONWARNINGS" in os.environ
|
assert "PYTHONWARNINGS" in os.environ
|
||||||
|
# Caps git's upward search at the config dir so an uninitialized or
|
||||||
|
# corrupt parent git repo can't break the framework's `git describe`.
|
||||||
|
assert str(CORE.config_dir) in os.environ["GIT_CEILING_DIRECTORIES"].split(
|
||||||
|
os.pathsep
|
||||||
|
)
|
||||||
|
|
||||||
# Check command was called correctly — runs PlatformIO as a subprocess
|
# Check command was called correctly — runs PlatformIO as a subprocess
|
||||||
# via the esphome.platformio.runner entry point.
|
# via the esphome.platformio.runner entry point.
|
||||||
|
|||||||
Reference in New Issue
Block a user