fix(tools): seed IDF_VERSION before idf.py parses dependencies.lock

Any idf.py invocation could hang indefinitely with no output while
spawning an unbounded chain of "idf.py --version" subprocesses,
eventually exhausting system memory.

During init_cli(), idf.py parses the project's dependencies.lock to
vet trusted component-provided idf_ext.py extensions. If the lock
contains a component whose manifest has an "if: idf_version" clause,
evaluating it calls idf-component-manager's _get_idf_version(). Outside
a CMake build the IDF_VERSION environment variable is not set, so that
function falls back to running "idf.py --version" as a subprocess,
which re-enters init_cli() and recurses without bound.

During a normal CMake build the component manager runs as a subprocess
that already has IDF_VERSION in its environment (see build/config.env),
so the fallback is never reached. The recursion happens only because
idf.py runs component-manager code in-process during its own CLI
startup, outside that context.

Seed IDF_VERSION into os.environ early in init_cli(), before any
dependencies.lock parsing, using the subprocess-free
idf_version_from_cmake() helper. This gives in-process component-manager
code the same IDF_VERSION a CMake build would provide.
This commit is contained in:
Roland Dobai
2026-05-15 17:43:56 +02:00
parent 0f4a3e8c2e
commit 490452a2a2
2 changed files with 201 additions and 109 deletions
+15
View File
@@ -44,6 +44,7 @@ try:
from idf_py_actions.tools import SHELL_COMPLETE_RUN
from idf_py_actions.tools import SHELL_COMPLETE_VAR
from idf_py_actions.tools import PropertyDict
from idf_py_actions.tools import _idf_version_from_cmake
from idf_py_actions.tools import debug_print_idf_version
from idf_py_actions.tools import get_target
from idf_py_actions.tools import merge_action_lists
@@ -725,6 +726,20 @@ def init_cli(verbose_output: Optional[List] = None) -> Any:
# Set `complete_var` to not existing environment variable name to prevent early cmd completion
project_dir = parse_project_dir(standalone_mode=False, complete_var='_IDF.PY_COMPLETE_NOT_EXISTING')
# Ensure IDF_VERSION is available for in-process component-manager code
# (e.g. dependencies.lock `if: idf_version` clauses). Outside a CMake build
# this env var is unset; without it idf-component-manager falls back to
# spawning `idf.py --version`, which re-enters here -> infinite recursion.
if 'IDF_VERSION' not in os.environ:
# Best-effort: if idf_version_from_cmake() returns None (corrupt/missing
# version.cmake) IDF_VERSION stays unset and the recursion guard does not apply.
idf_ver = _idf_version_from_cmake() # 'vX.Y.Z' or None; regex parse, no subprocess
if idf_ver:
# Strip the leading 'v' to match the value a CMake build provides
# (see tools/cmake/version.cmake); component-manager code consumes
# this env var verbatim and cannot parse a 'v' prefix.
os.environ['IDF_VERSION'] = idf_ver.lstrip('v')
all_actions: Dict = {}
# Load extensions from components dir
idf_py_extensions_path = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_py_actions')