Files
esp-idf/tools/test_build_system/test_rebuild_configdep.py
T
Jan Beran 3abb4c095b test: add esp-idf-configdep integration tests
Add build-level tests verifying configdep selective rebuild behavior:
- test_configdep.py: selective rebuild (only affected .obj rebuilt) and
  ELF string verification after Kconfig toggle
- test_rebuild_configdep.py: full rebuild graph tests with configdep enabled,
  including the two-build "settle" pattern for .cdep stub timestamps

Place the configdep test app in tools/test_build_system/configdep_test_app/
alongside the build system test infrastructure. Split configdep-specific
rebuild coverage from pure CMake/Ninja rebuild tests (test_rebuild.py runs
with configdep disabled via a module-local idf_py fixture).
2026-06-02 01:26:10 +08:00

109 lines
5.2 KiB
Python

# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
"""Rebuild tests with esp-idf-configdep enabled (default idf.py behavior).
esp-idf-configdep rewrites compiler .d files to depend on per-option
build/config/.../*.cdep stubs instead of sdkconfig.h. When a referenced
.cdep file does not exist yet, the wrapper creates it **after** the real
compiler has written the .obj. Those stubs then have a newer mtime than the
object. On the **next** Ninja run, those .cdep paths are prerequisites in the
merged dependency graph, so Ninja correctly treats the object as out of date and
rebuilds it—even though nothing in the source tree or sdkconfig changed.
That one-time “settling” wave is not a CMake bug; it is an artifact of how
configdep materializes stubs relative to object timestamps. The tests in
test_rebuild.py therefore disable configdep to exercise pure CMake/Ninja
incremental behavior. Here we keep configdep **on** and run **two** full
idf.py build invocations before any mtime baseline so stub/object mtimes are
consistent; then we apply the same coarse-grained checks as the classic rebuild
tests (plus relaxed expectations where configdep legitimately skips compiling
TUs that do not reference changed CONFIG_* symbols).
"""
import logging
import os
from pathlib import Path
import pytest
from test_build_system_helpers import ALL_ARTIFACTS_BUILDV1
from test_build_system_helpers import ALL_ARTIFACTS_BUILDV2
from test_build_system_helpers import APP_BINS
from test_build_system_helpers import BOOTLOADER_BINS
from test_build_system_helpers import BUILD_SNAPSHOT_EXCLUDE
from test_build_system_helpers import PARTITION_BIN
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import get_snapshot
from test_build_system_helpers import rebuild_and_check
from test_build_system_helpers import replace_in_file
def _settle_configdep_graph(idf_py: IdfPyFunc) -> None:
"""Two full builds so ``.cdep`` stubs created by configdep are not newer than ``.obj`` for the next graph walk."""
logging.info('settle (configdep): first full build')
idf_py('build')
logging.info('settle (configdep): second full build reconciles obj vs .cdep mtimes')
idf_py('build')
@pytest.mark.usefixtures('test_app_copy')
def test_rebuild_no_changes_with_configdep(idf_py: IdfPyFunc, request: pytest.FixtureRequest) -> None:
"""No-op rebuild: with configdep on, baseline snapshot only after two settle builds (see module docstring)."""
_settle_configdep_graph(idf_py)
logging.info('baseline snapshot after settle')
all_build_files = get_snapshot('build/**/*', exclude_patterns=BUILD_SNAPSHOT_EXCLUDE)
logging.info('check that all build artifacts were generated')
all_artifacts = ALL_ARTIFACTS_BUILDV2 if request.config.getoption('buildv2', False) else ALL_ARTIFACTS_BUILDV1
for artifact in all_artifacts:
assert Path(artifact).exists()
logging.info('noop build with configdep; mtimes under baseline should match')
idf_py('build')
all_build_files_after = get_snapshot('build/**/*', exclude_patterns=BUILD_SNAPSHOT_EXCLUDE)
all_build_files_after.assert_same(all_build_files)
@pytest.mark.usefixtures('test_app_copy')
def test_rebuild_source_files_with_configdep(idf_py: IdfPyFunc) -> None:
"""Same selective-rebuild checks as ``test_rebuild_source_files``, after configdep settle builds."""
idf_path = Path(os.environ['IDF_PATH'])
_settle_configdep_graph(idf_py)
logging.info('updating a component source file rebuilds only that component')
component_files_patterns = [
'build/esp-idf/esp_system/libesp_system.a',
'build/esp-idf/esp_system/CMakeFiles/__idf_esp_system.dir/port/cpu_start.c.obj',
]
other_files_patterns = [
'build/esp-idf/lwip/liblwip.a',
'build/esp-idf/freertos/libfreertos.a',
*BOOTLOADER_BINS,
*PARTITION_BIN,
]
(idf_path / 'components/esp_system/port/cpu_start.c').touch()
rebuild_and_check(idf_py, component_files_patterns, other_files_patterns)
logging.info('changing a bootloader source file rebuilds the bootloader')
(idf_path / 'components/bootloader/subproject/main/bootloader_start.c').touch()
rebuild_and_check(idf_py, BOOTLOADER_BINS, APP_BINS + PARTITION_BIN)
logging.info('changing the partitions CSV file rebuilds only the partition table')
(idf_path / 'components/partition_table/partitions_singleapp.csv').touch()
rebuild_and_check(idf_py, PARTITION_BIN, APP_BINS + BOOTLOADER_BINS)
logging.info(
'sdkconfig update: binaries and generated headers must refresh (objects are selective under configdep)'
)
sdkconfig_files = ['build/config/sdkconfig.h', 'build/config/sdkconfig.json']
replace_in_file('sdkconfig', '# CONFIG_FREERTOS_UNICORE is not set', 'CONFIG_FREERTOS_UNICORE=y')
rebuild_and_check(idf_py, APP_BINS + BOOTLOADER_BINS + sdkconfig_files, PARTITION_BIN)
logging.info('Updating project CMakeLists.txt triggers app re-link at minimum')
replace_in_file(
'CMakeLists.txt',
'# placeholder_after_include_project_cmake',
'add_compile_options("-DUSELESS_MACRO_DOES_NOTHING=1")',
)
rebuild_and_check(idf_py, APP_BINS, PARTITION_BIN + BOOTLOADER_BINS + sdkconfig_files)