From 3abb4c095bd1d86fd69558b345b95fb141c0c8b4 Mon Sep 17 00:00:00 2001 From: Jan Beran Date: Fri, 6 Mar 2026 13:40:35 +0100 Subject: [PATCH] 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). --- .../configdep_test_app/CMakeLists.txt | 9 ++ .../configdep_test_app/README.md | 6 + .../configdep_test_app/main/CMakeLists.txt | 2 + .../configdep_test_app/main/Kconfig.projbuild | 5 + .../configdep_test_app/main/configdep_main.c | 22 ++++ .../configdep_test_app/main/unrelated.c | 17 +++ .../test_build_system_helpers/__init__.py | 4 + .../rebuild_utils.py | 24 ++++ tools/test_build_system/test_configdep.py | 80 +++++++++++++ tools/test_build_system/test_rebuild.py | 69 +++++------ .../test_rebuild_configdep.py | 108 ++++++++++++++++++ 11 files changed, 312 insertions(+), 34 deletions(-) create mode 100644 tools/test_build_system/configdep_test_app/CMakeLists.txt create mode 100644 tools/test_build_system/configdep_test_app/README.md create mode 100644 tools/test_build_system/configdep_test_app/main/CMakeLists.txt create mode 100644 tools/test_build_system/configdep_test_app/main/Kconfig.projbuild create mode 100644 tools/test_build_system/configdep_test_app/main/configdep_main.c create mode 100644 tools/test_build_system/configdep_test_app/main/unrelated.c create mode 100644 tools/test_build_system/test_build_system_helpers/rebuild_utils.py create mode 100644 tools/test_build_system/test_configdep.py create mode 100644 tools/test_build_system/test_rebuild_configdep.py diff --git a/tools/test_build_system/configdep_test_app/CMakeLists.txt b/tools/test_build_system/configdep_test_app/CMakeLists.txt new file mode 100644 index 00000000000..19decd6871e --- /dev/null +++ b/tools/test_build_system/configdep_test_app/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.22) + +# placeholder_before_include_project_cmake + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# placeholder_after_include_project_cmake + +project(configdep_test_app) diff --git a/tools/test_build_system/configdep_test_app/README.md b/tools/test_build_system/configdep_test_app/README.md new file mode 100644 index 00000000000..f417621981d --- /dev/null +++ b/tools/test_build_system/configdep_test_app/README.md @@ -0,0 +1,6 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | + +# Configdep test application + +Minimal ESP-IDF project used by ``tools/test_build_system/test_configdep.py`` to exercise ``esp-idf-configdep`` selective rebuilds and to assert that the built firmware reflects Kconfig toggles (via strings embedded in the ELF). diff --git a/tools/test_build_system/configdep_test_app/main/CMakeLists.txt b/tools/test_build_system/configdep_test_app/main/CMakeLists.txt new file mode 100644 index 00000000000..39d1003c5d1 --- /dev/null +++ b/tools/test_build_system/configdep_test_app/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "configdep_main.c" "unrelated.c" + INCLUDE_DIRS "") diff --git a/tools/test_build_system/configdep_test_app/main/Kconfig.projbuild b/tools/test_build_system/configdep_test_app/main/Kconfig.projbuild new file mode 100644 index 00000000000..b22487eab66 --- /dev/null +++ b/tools/test_build_system/configdep_test_app/main/Kconfig.projbuild @@ -0,0 +1,5 @@ +menu "Configdep Test" + config TEST_CONFIGDEP_OPTION + bool "Test configdep option" + default y +endmenu diff --git a/tools/test_build_system/configdep_test_app/main/configdep_main.c b/tools/test_build_system/configdep_test_app/main/configdep_main.c new file mode 100644 index 00000000000..e2476f098b6 --- /dev/null +++ b/tools/test_build_system/configdep_test_app/main/configdep_main.c @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern void unrelated_function(void); + +void app_main(void) +{ +#if CONFIG_TEST_CONFIGDEP_OPTION + printf("CONFIGDEP_OPTION: ENABLED\n"); +#else + printf("CONFIGDEP_OPTION: DISABLED\n"); +#endif + unrelated_function(); + vTaskDelay(pdMS_TO_TICKS(1000)); +} diff --git a/tools/test_build_system/configdep_test_app/main/unrelated.c b/tools/test_build_system/configdep_test_app/main/unrelated.c new file mode 100644 index 00000000000..2dff8dd964f --- /dev/null +++ b/tools/test_build_system/configdep_test_app/main/unrelated.c @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* + * Second TU in ``main``: only uses CONFIG_IDF_TARGET from sdkconfig—no conditional + * on the optional symbol defined for configdep_main.c (see ``Kconfig.projbuild``). + * Used by tests to snapshot this ``.obj`` alongside ``configdep_main``. + */ +#include +#include "sdkconfig.h" + +void unrelated_function(void) +{ + printf("IDF target: %s\n", CONFIG_IDF_TARGET); +} diff --git a/tools/test_build_system/test_build_system_helpers/__init__.py b/tools/test_build_system/test_build_system_helpers/__init__.py index d5166e7a1ea..0432ea96249 100644 --- a/tools/test_build_system/test_build_system_helpers/__init__.py +++ b/tools/test_build_system/test_build_system_helpers/__init__.py @@ -20,6 +20,8 @@ from .idf_utils import get_idf_build_env from .idf_utils import run_cmake from .idf_utils import run_cmake_and_build from .idf_utils import run_idf_py +from .rebuild_utils import BUILD_SNAPSHOT_EXCLUDE +from .rebuild_utils import rebuild_and_check from .snapshot import Snapshot from .snapshot import get_snapshot @@ -46,4 +48,6 @@ __all__ = [ 'file_contains', 'bin_file_contains', 'bin_files_differ', + 'BUILD_SNAPSHOT_EXCLUDE', + 'rebuild_and_check', ] diff --git a/tools/test_build_system/test_build_system_helpers/rebuild_utils.py b/tools/test_build_system/test_build_system_helpers/rebuild_utils.py new file mode 100644 index 00000000000..c40f4aacacc --- /dev/null +++ b/tools/test_build_system/test_build_system_helpers/rebuild_utils.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +"""Shared helpers and constants for rebuild tests (test_rebuild*.py).""" + +from .idf_utils import IdfPyFunc +from .snapshot import get_snapshot + +# Paths excluded from mtime snapshots (volatile or external-project stamps). +BUILD_SNAPSHOT_EXCLUDE = [ + 'build/log/*', + 'build/CMakeFiles/bootloader-complete', + 'build/bootloader-prefix/src/bootloader-stamp/bootloader-done', +] + + +def rebuild_and_check( + idf_py: IdfPyFunc, should_be_rebuilt: str | list[str], should_not_be_rebuilt: str | list[str] +) -> None: + """Assert that files matching *should_be_rebuilt* change and *should_not_be_rebuilt* stay the same after a build.""" + snapshot_should_be_rebuilt = get_snapshot(should_be_rebuilt) + snapshot_should_not_be_rebuilt = get_snapshot(should_not_be_rebuilt) + idf_py('build') + snapshot_should_be_rebuilt.assert_different(get_snapshot(should_be_rebuilt)) + snapshot_should_not_be_rebuilt.assert_same(get_snapshot(should_not_be_rebuilt)) diff --git a/tools/test_build_system/test_configdep.py b/tools/test_build_system/test_configdep.py new file mode 100644 index 00000000000..85a4c119936 --- /dev/null +++ b/tools/test_build_system/test_configdep.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import logging +import os +from pathlib import Path + +import pytest +from test_build_system_helpers import IdfPyFunc +from test_build_system_helpers import append_to_file +from test_build_system_helpers import bin_file_contains +from test_build_system_helpers import get_snapshot +from test_build_system_helpers import replace_in_file + +CONFIGDEP_TEST_APP = 'tools/test_build_system/configdep_test_app' + +_MAIN_OBJ_DIR = ('build', 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir') +MAIN_OBJ = os.path.join(*_MAIN_OBJ_DIR, 'configdep_main.c.obj') +UNRELATED_OBJ = os.path.join(*_MAIN_OBJ_DIR, 'unrelated.c.obj') +CONFIGDEP_ELF = Path(os.path.join('build', 'configdep_test_app.elf')) + + +@pytest.mark.test_app_copy(CONFIGDEP_TEST_APP) +@pytest.mark.usefixtures('test_app_copy') +def test_configdep_selective_rebuild(idf_py: IdfPyFunc) -> None: + """Verify selective rebuild when a Kconfig option toggles. + + ``esp-idf-configdep`` scans each translation unit's source for ``CONFIG_*`` + tokens and appends matching ``.cdep`` stubs to the compiler depfile. Avoid + spelling projbuild-only symbols (even in comments) in ``unrelated.c``, or the + scanner adds a spurious ``option.cdep`` edge and Ninja rebuilds that ``.obj``. + """ + logging.info('initial build with configdep enabled (default)') + idf_py('build') + + logging.info('toggle CONFIG_TEST_CONFIGDEP_OPTION from y to n') + replace_in_file('sdkconfig', 'CONFIG_TEST_CONFIGDEP_OPTION=y', '# CONFIG_TEST_CONFIGDEP_OPTION is not set') + + snapshot_main = get_snapshot(MAIN_OBJ) + snapshot_unrelated = get_snapshot(UNRELATED_OBJ) + + logging.info('rebuild after config change') + idf_py('build') + + logging.info('configdep_main.c.obj must be rebuilt (references changed option)') + snapshot_main.assert_different(get_snapshot(MAIN_OBJ)) + + logging.info('unrelated.c.obj must not be rebuilt (no CONFIG_* token for that option in source)') + snapshot_unrelated.assert_same(get_snapshot(UNRELATED_OBJ)) + + +@pytest.mark.test_app_copy(CONFIGDEP_TEST_APP) +@pytest.mark.usefixtures('test_app_copy') +def test_configdep_no_rebuild_without_change(idf_py: IdfPyFunc) -> None: + logging.info('initial build with configdep enabled (default)') + idf_py('build') + + snapshot_main = get_snapshot(MAIN_OBJ) + snapshot_unrelated = get_snapshot(UNRELATED_OBJ) + + logging.info('rebuild with no config changes') + idf_py('build') + + logging.info('neither object file should be rebuilt') + snapshot_main.assert_same(get_snapshot(MAIN_OBJ)) + snapshot_unrelated.assert_same(get_snapshot(UNRELATED_OBJ)) + + +@pytest.mark.test_app_copy(CONFIGDEP_TEST_APP) +@pytest.mark.usefixtures('test_app_copy') +def test_configdep_elf_strings_track_sdkconfig_toggle(idf_py: IdfPyFunc) -> None: + """After rebuild, the firmware string matches the final Kconfig (host-side ELF check).""" + idf_py('build') + assert CONFIGDEP_ELF.is_file() + assert bin_file_contains(CONFIGDEP_ELF, b'CONFIGDEP_OPTION: ENABLED') + assert not bin_file_contains(CONFIGDEP_ELF, b'CONFIGDEP_OPTION: DISABLED') + + append_to_file('sdkconfig', '\n# CONFIG_TEST_CONFIGDEP_OPTION is not set\n') + idf_py('build') + assert bin_file_contains(CONFIGDEP_ELF, b'CONFIGDEP_OPTION: DISABLED') + assert not bin_file_contains(CONFIGDEP_ELF, b'CONFIGDEP_OPTION: ENABLED') diff --git a/tools/test_build_system/test_rebuild.py b/tools/test_build_system/test_rebuild.py index 355c6b69c6e..c6d4cf52904 100644 --- a/tools/test_build_system/test_rebuild.py +++ b/tools/test_build_system/test_rebuild.py @@ -1,20 +1,51 @@ # SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # These tests check whether the build system rebuilds some files or not -# depending on the changes to the project. +# depending on the changes to the project. Configdep is disabled here so +# mtime assertions reflect CMake/Ninja only; see test_rebuild_configdep.py for +# the same checks with configdep enabled (after settle builds). import logging import os +import subprocess from pathlib import Path +from typing import cast 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_idf_build_env 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 +from test_build_system_helpers import run_idf_py + + +@pytest.fixture +def idf_py(monkeypatch: pytest.MonkeyPatch) -> IdfPyFunc: + """Use ``idf.py`` with esp-idf-configdep disabled for this module. + + These tests assert Ninja rebuild granularity via file mtimes. The + ``esp-idf-configdep`` wrapper can create per-option ``.cdep`` stubs after the + real compiler has written ``.obj`` files, so on the next Ninja run inputs can + look newer than outputs and trigger spurious rebuilds. That behavior is + covered separately in ``test_rebuild_configdep.py`` with a settle-build + pattern. Here we exercise the core CMake/Ninja dependency graph only. + """ + monkeypatch.setenv('IDF_CONFIGDEP_ENABLE', '0') + env = get_idf_build_env(os.environ['IDF_PATH']) + + def result(*args: str, check: bool = True, input_str: str | None = None) -> subprocess.CompletedProcess[str]: + return cast( + subprocess.CompletedProcess[str], + run_idf_py(*args, env=env, workdir=os.getcwd(), check=check, input_str=input_str), + ) + + return result @pytest.mark.usefixtures('test_app_copy') @@ -23,14 +54,7 @@ def test_rebuild_no_changes(idf_py: IdfPyFunc, request: pytest.FixtureRequest) - idf_py('build') logging.info('get the first snapshot') # excluding the 'log' subdirectory here since it changes after every build - all_build_files = get_snapshot( - 'build/**/*', - exclude_patterns=[ - 'build/log/*', - 'build/CMakeFiles/bootloader-complete', - 'build/bootloader-prefix/src/bootloader-stamp/bootloader-done', - ], - ) + 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 @@ -40,33 +64,10 @@ def test_rebuild_no_changes(idf_py: IdfPyFunc, request: pytest.FixtureRequest) - logging.info('build again with no changes') idf_py('build') # if there are no changes, nothing gets rebuilt - all_build_files_after_rebuild = get_snapshot( - 'build/**/*', - exclude_patterns=[ - 'build/log/*', - 'build/CMakeFiles/bootloader-complete', - 'build/bootloader-prefix/src/bootloader-stamp/bootloader-done', - ], - ) + all_build_files_after_rebuild = get_snapshot('build/**/*', exclude_patterns=BUILD_SNAPSHOT_EXCLUDE) all_build_files_after_rebuild.assert_same(all_build_files) -def rebuild_and_check( - idf_py: IdfPyFunc, should_be_rebuilt: str | list[str], should_not_be_rebuilt: str | list[str] -) -> None: - """ - Helper function for the test cases below. - Asserts that the files matching 'should_be_rebuilt' patterns are rebuilt - and files matching 'should_not_be_rebuilt' patterns aren't, after - touching (updating the mtime) of the given 'file_to_touch' and rebuilding. - """ - snapshot_should_be_rebuilt = get_snapshot(should_be_rebuilt) - snapshot_should_not_be_rebuilt = get_snapshot(should_not_be_rebuilt) - idf_py('build') - snapshot_should_be_rebuilt.assert_different(get_snapshot(should_be_rebuilt)) - snapshot_should_not_be_rebuilt.assert_same(get_snapshot(should_not_be_rebuilt)) - - # For this and the following test function, there are actually multiple logical # tests in one test function. It would be better to have every check in a separate # test case, but that would mean doing a full clean build each time. Having a few @@ -104,7 +105,7 @@ def test_rebuild_source_files(idf_py: IdfPyFunc) -> None: # pick on .c, .cpp, .S file which includes sdkconfig.h: obj_files = [ 'build/esp-idf/esp_libc/CMakeFiles/__idf_esp_libc.dir/newlib_init.c.obj', - 'build/esp-idf/nvs_flash/CMakeFiles/__idf_nvs_flash.dir/src/nvs_api.cpp.obj' + 'build/esp-idf/nvs_flash/CMakeFiles/__idf_nvs_flash.dir/src/nvs_api.cpp.obj', 'build/esp-idf/esp_system/CMakeFiles/__idf_esp_system.dir/port/arch/xtensa/panic_handler_asm.S.obj', ] sdkconfig_files = ['build/config/sdkconfig.h', 'build/config/sdkconfig.json'] diff --git a/tools/test_build_system/test_rebuild_configdep.py b/tools/test_build_system/test_rebuild_configdep.py new file mode 100644 index 00000000000..72d401a5e9e --- /dev/null +++ b/tools/test_build_system/test_rebuild_configdep.py @@ -0,0 +1,108 @@ +# 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)