mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat: Adopt rebuild optimization tool (configdep) in cmakev2
This commit is contained in:
@@ -21,11 +21,6 @@ set(CMAKE_MODULE_PATH
|
||||
# for both cmakev1 and cmakev2.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
# Suppress CMake warning: "Manually-specified variables were not used by the project: CONFIGDEP_ENABLE"
|
||||
# (CONFIGDEP_ENABLE is passed by idf.py but only used in the cmake v1 project() flow in project.cmake)
|
||||
# FIXME: When cmakev2 will start supporting configdep, this can be removed.
|
||||
set(_idf_ignore_configdep_enable "${CONFIGDEP_ENABLE}")
|
||||
|
||||
# The gdbinit.cmake file from cmakev1 contains a single function,
|
||||
# __generate_gdbinit, which is used in the generation of
|
||||
# project_description.json.
|
||||
@@ -376,22 +371,62 @@ function(__init_toolchain)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__init_ccache()
|
||||
__init_compiler_launchers()
|
||||
|
||||
Enable ccache if requested through CCACHE_ENABLE.
|
||||
Build the compiler launcher chain and apply it to
|
||||
CMAKE_{C,CXX,ASM}_COMPILER_LAUNCHER. The chain runs esp-idf-configdep first
|
||||
(when configdep is enabled) followed by ccache (when enabled).
|
||||
#]]
|
||||
function(__init_ccache)
|
||||
if(NOT CCACHE_ENABLE)
|
||||
function(__init_compiler_launchers)
|
||||
set(launcher_chain "")
|
||||
|
||||
# esp-idf-configdep goes first in the chain.
|
||||
# Gate on the same esp-idf-kconfig version check as --output cdep_tree in
|
||||
# kconfig.cmake so the launcher is never enabled without the .cdep tree.
|
||||
if(CONFIGDEP_ENABLE)
|
||||
idf_build_get_property(python PYTHON)
|
||||
__check_python_package_min_version(
|
||||
${python} esp-idf-kconfig "${CONFIGDEP_MIN_KCONFIG_VERSION}" _kconfig_version_ok)
|
||||
if(_kconfig_version_ok)
|
||||
find_program(CONFIGDEP_FOUND esp-idf-configdep)
|
||||
if(CONFIGDEP_FOUND)
|
||||
idf_msg("esp-idf-configdep will be used for faster recompilation")
|
||||
list(APPEND launcher_chain "esp-idf-configdep")
|
||||
else()
|
||||
idf_warn("esp-idf-configdep enabled but not found. "
|
||||
"Run 'eim fix' to update esp-idf-kconfig.")
|
||||
endif()
|
||||
else()
|
||||
idf_warn("esp-idf-configdep not supported by esp-idf-kconfig "
|
||||
"(>= ${CONFIGDEP_MIN_KCONFIG_VERSION} required). "
|
||||
"Run 'eim fix' to update esp-idf-kconfig.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ccache goes second in the chain.
|
||||
if(CCACHE_ENABLE)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
idf_msg("ccache will be used for faster recompilation")
|
||||
list(APPEND launcher_chain "ccache")
|
||||
else()
|
||||
idf_warn("enabled ccache in build but ccache program not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT launcher_chain)
|
||||
idf_msg("No compiler launcher chain will be used.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
idf_msg("ccache will be used for faster recompilation")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
else()
|
||||
idf_warn("enabled ccache in build but ccache program not found")
|
||||
endif()
|
||||
# Apply to all languages. Set in the caller's (project top-level) directory
|
||||
# scope so component targets created later in idf_project_init() inherit it.
|
||||
set(CMAKE_C_COMPILER_LAUNCHER "${launcher_chain}" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${launcher_chain}" PARENT_SCOPE)
|
||||
set(CMAKE_ASM_COMPILER_LAUNCHER "${launcher_chain}" PARENT_SCOPE)
|
||||
|
||||
string(REPLACE ";" " -> " launcher_display "${launcher_chain}")
|
||||
idf_msg("Compiler launcher chain: ${launcher_display}")
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
@@ -761,8 +796,8 @@ __init_idf_target()
|
||||
# Set IDF_TOOLCHAIN, IDF_TOOLCHAIN_FILE and CMAKE_TOOLCHAIN_FILE.
|
||||
__init_toolchain()
|
||||
|
||||
# Enable ccache if requested.
|
||||
__init_ccache()
|
||||
# Set up the compiler launcher chain (esp-idf-configdep, ccache) if requested.
|
||||
__init_compiler_launchers()
|
||||
|
||||
#[[
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ set(ESP_MENUCONFIG_MIN_KCONFIG_VERSION "3.1.0")
|
||||
# (fused menuconfig + deprecated-options post-processing in a single invocation).
|
||||
set(MENUCONFIG_INLINE_MIN_KCONFIG_VERSION "3.9.0")
|
||||
|
||||
# Minimum esp-idf-kconfig version required for cdep_tree / configdep (--output cdep_tree)
|
||||
set(CONFIGDEP_MIN_KCONFIG_VERSION "3.6.0")
|
||||
|
||||
#[[
|
||||
__init_kconfig()
|
||||
|
||||
@@ -707,6 +710,17 @@ function(__generate_kconfig_outputs)
|
||||
list(APPEND kconfgen_outputs_cmd --output config "${sdkconfig}")
|
||||
endif()
|
||||
|
||||
# If configdep is enabled and esp-idf-kconfig supports cdep_tree, emit one
|
||||
# .cdep stub per config option into config_dir (next to sdkconfig.h) so that
|
||||
# esp-idf-configdep can rewrite compiler depfiles for selective rebuilds.
|
||||
# Requires esp-idf-kconfig >= CONFIGDEP_MIN_KCONFIG_VERSION.
|
||||
idf_build_get_property(python PYTHON)
|
||||
__check_python_package_min_version(
|
||||
${python} esp-idf-kconfig "${CONFIGDEP_MIN_KCONFIG_VERSION}" CONFIGDEP_SUPPORTED)
|
||||
if(CONFIGDEP_SUPPORTED AND CONFIGDEP_ENABLE)
|
||||
list(APPEND kconfgen_outputs_cmd --output cdep_tree "${config_dir}")
|
||||
endif()
|
||||
|
||||
idf_build_set_property(__KCONFGEN_OUTPUTS_CMD "${kconfgen_outputs_cmd}")
|
||||
|
||||
# Generate Kconfig outputs using kconfgen
|
||||
|
||||
@@ -13,15 +13,49 @@ 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'))
|
||||
|
||||
|
||||
def _main_obj_dir(request: pytest.FixtureRequest) -> str:
|
||||
# cmakev2 names the main component target ``_idf_main``; cmakev1 uses ``__idf_main``.
|
||||
target_dir = '_idf_main.dir' if request.config.getoption('buildv2', False) else '__idf_main.dir'
|
||||
return os.path.join('build', 'esp-idf', 'main', 'CMakeFiles', target_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def main_obj(request: pytest.FixtureRequest) -> str:
|
||||
return os.path.join(_main_obj_dir(request), 'configdep_main.c.obj')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unrelated_obj(request: pytest.FixtureRequest) -> str:
|
||||
return os.path.join(_main_obj_dir(request), 'unrelated.c.obj')
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _select_build_system(test_app_copy: Path, request: pytest.FixtureRequest) -> None:
|
||||
"""Build the shared configdep app with the requested build system.
|
||||
|
||||
The app sources are build-system agnostic; only the top-level
|
||||
``CMakeLists.txt`` differs. When running with ``--buildv2``, switch it to the
|
||||
cmakev2 entry point so the same test exercises configdep on both build systems.
|
||||
"""
|
||||
if not request.config.getoption('buildv2', False):
|
||||
return
|
||||
replace_in_file(
|
||||
'CMakeLists.txt',
|
||||
'include($ENV{IDF_PATH}/tools/cmake/project.cmake)',
|
||||
'include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)',
|
||||
)
|
||||
replace_in_file(
|
||||
'CMakeLists.txt',
|
||||
'project(configdep_test_app)',
|
||||
'project(configdep_test_app C CXX ASM)\nidf_project_default()',
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.test_app_copy(CONFIGDEP_TEST_APP)
|
||||
@pytest.mark.usefixtures('test_app_copy')
|
||||
def test_configdep_selective_rebuild(idf_py: IdfPyFunc) -> None:
|
||||
def test_configdep_selective_rebuild(idf_py: IdfPyFunc, main_obj: str, unrelated_obj: str) -> None:
|
||||
"""Verify selective rebuild when a Kconfig option toggles.
|
||||
|
||||
``esp-idf-configdep`` scans each translation unit's source for ``CONFIG_*``
|
||||
@@ -35,38 +69,36 @@ def test_configdep_selective_rebuild(idf_py: IdfPyFunc) -> None:
|
||||
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)
|
||||
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))
|
||||
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))
|
||||
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:
|
||||
def test_configdep_no_rebuild_without_change(idf_py: IdfPyFunc, main_obj: str, unrelated_obj: str) -> None:
|
||||
logging.info('initial build with configdep enabled (default)')
|
||||
idf_py('build')
|
||||
|
||||
snapshot_main = get_snapshot(MAIN_OBJ)
|
||||
snapshot_unrelated = get_snapshot(UNRELATED_OBJ)
|
||||
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))
|
||||
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')
|
||||
|
||||
Reference in New Issue
Block a user