fix(cmakev2/kconfig): preserve sdkconfig options from managed components

The sdkconfig file may contain configuration options defined in Kconfig
files of managed components. Since kconfgen runs before the component
manager fetches these components, the Kconfig definitions for managed
component options are not yet available. The kconfgen --output config
flag regenerates sdkconfig from kconfiglib's internal state, which only
knows about options with loaded Kconfig definitions. This causes unknown
options (i.e., those from managed components) to be silently dropped
from sdkconfig during intermediate regeneration rounds.

Note that kconfgen's --config flag (used for reading sdkconfig) only
performs deprecated option name replacement and does NOT drop unknown
options. The problem is exclusively in --output config, which writes a
fresh sdkconfig from the parsed Kconfig tree state.

Fix this by introducing a __SDKCONFIG_ORIG build property that provides
an indirection layer for the --config input path:

- Before the component manager runs: __SDKCONFIG_ORIG points to a copy
  of the original sdkconfig (build/sdkconfig.orig), created by the new
  __create_sdkconfig_orig_copy() function. This copy preserves all
  original options, including those from managed components.

- During intermediate kconfgen runs: --config reads from the preserved
  copy (so unknown options survive as input), while --output config
  writes to the real sdkconfig (unknown options may be dropped there,
  but this is harmless since kconfgen always reads from the copy).

- After the component manager completes: __SDKCONFIG_ORIG is reset to
  point to the real sdkconfig and __BASE_KCONFGEN_CMD is rebuilt, so
  that subsequent operations (menuconfig, save-defconfig, confserver)
  read and write the actual sdkconfig file directly.

The flow is:

  __create_sdkconfig_orig_copy()
    -> __SDKCONFIG_ORIG = build/sdkconfig.orig

  __generate_sdkconfig()
    -> --config build/sdkconfig.orig --output config project/sdkconfig

  __fetch_components_from_registry():
    loop:
      download_components()
      __generate_sdkconfig()
        -> --config build/sdkconfig.orig --output config project/sdkconfig
      if success: break
    endloop
    -> __SDKCONFIG_ORIG = project/sdkconfig
    -> rebuild __BASE_KCONFGEN_CMD

  idf_create_menuconfig() / save-defconfig / confserver
    -> uses --config project/sdkconfig (the real file)

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata
2026-03-20 08:13:26 +01:00
parent 512dacab6a
commit 2aacc2ad0c
4 changed files with 85 additions and 1 deletions
@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import logging
from pathlib import Path
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import file_contains
def test_sdkconfig_preserves_managed_component_options(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('sdkconfig options from managed components should be preserved during reconfigure')
# Create a local managed component outside the project directory.
# Since it is referenced via path in idf_component.yml, the component
# manager processes it as a managed component whose Kconfig is only
# available after the CM run -- i.e. after the initial kconfgen round.
managed_comp_dir = test_app_copy / '..' / 'managed_comp'
managed_comp_dir.mkdir(parents=True)
(managed_comp_dir / 'CMakeLists.txt').write_text('idf_component_register()\n')
(managed_comp_dir / 'Kconfig').write_text(
'config MANAGED_COMP_TEST_OPTION\n int "Managed component test option"\n default 10\n'
)
# Reference the local component as a managed dependency
(test_app_copy / 'main' / 'idf_component.yml').write_text(
'dependencies:\n managed_comp:\n path: "../../managed_comp"\n'
)
# Write a non-default value directly into sdkconfig (not sdkconfig.defaults).
# Before the fix, kconfgen would drop this unknown option via --output config
# during the first generation round (before the component manager makes the
# managed component's Kconfig available), causing the value to revert to the
# default (10) in the final sdkconfig.
(test_app_copy / 'sdkconfig').write_text('CONFIG_MANAGED_COMP_TEST_OPTION=30\n')
idf_py('reconfigure')
assert file_contains(test_app_copy / 'sdkconfig', 'CONFIG_MANAGED_COMP_TEST_OPTION=30')