# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 include_guard(GLOBAL) cmake_minimum_required(VERSION 3.22) # Update the CMAKE_MODULE_PATH to ensure that additional cmakev2 build system # modules can be included. The third_party directory from cmakev1 is also # included for third-party modules shared with cmakev1. set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}/../cmake/third_party" ${CMAKE_MODULE_PATH}) # The version.cmake file contains the IDF_VERSION variables, which are the same # 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. include(${CMAKE_CURRENT_LIST_DIR}/../cmake/gdbinit.cmake) # The openocd.cmake file from cmakev1 contains a single function, # __get_openocd_options, which is used in the generation of # project_description.json. include(${CMAKE_CURRENT_LIST_DIR}/../cmake/openocd.cmake) # The depgraph.cmake file from cmakev1 contains helper functions for generating # a component dependency graph. Let's reuse these functions in the # idf_build_generate_depgraph function. include(${CMAKE_CURRENT_LIST_DIR}/../cmake/depgraph.cmake) # The err_codes.cmake file from cmakev1 provides idf_define_esp_err_codes(), # which generates per-component error code tables placed in a link-time array. include(${CMAKE_CURRENT_LIST_DIR}/../cmake/err_codes.cmake) include(component) include(build) include(kconfig) include(project) include(manager) include(compat) include(ldgen) include(dfu) include(uf2) include(size) include(GetGitRevisionDescription) # For backward compatibility, since externalproject_add is used by # project_include.cmake in the bootloader component. The ExternalProject # should probably be included there instead. include(ExternalProject) #[[api .. cmakev2:variable:: IDF_BUILD_V2 Set to ``y`` when the project is built with the CMake-based build system v2. Use it to write component code that works with both v1 and v2, for example ``if(IDF_BUILD_V2)``. It is also exported as an environment variable and stored as a build property. .. cmakev2:variable:: IDF_BUILD_VER The major version number of the build system, ``2`` for v2. .. cmakev2:variable:: IDF_BUILD_VER_TAG The build system version tag, ``v2`` for v2. #]] #[[ __init_build_version() Set the global variables IDF_BUILD_V2, IDF_BUILD_VER and IDF_BUILD_VER_TAG, as well as the build properties and environmental variables. #]] function(__init_build_version) set(IDF_BUILD_V2 y PARENT_SCOPE) set(IDF_BUILD_VER 2 PARENT_SCOPE) set(IDF_BUILD_VER_TAG "v2" PARENT_SCOPE) idf_build_set_property(IDF_BUILD_V2 y) idf_build_set_property(IDF_BUILD_VER 2) idf_build_set_property(IDF_BUILD_VER_TAG "v2") set(ENV{IDF_BUILD_V2} y) set(ENV{IDF_BUILD_VER} 2) set(ENV{IDF_BUILD_VER_TAG} "v2") endfunction() #[[ __init_idf_path() Determine the IDF_PATH value, either from the IDF_PATH environmental variable or based on the location of this file. Also check there is no inconsistency between the two. Set the IDF_PATH global variable, environment variable and build property. #]] function(__init_idf_path) get_filename_component(idf_path_infer "${CMAKE_CURRENT_LIST_DIR}/../.." REALPATH) if(NOT DEFINED ENV{IDF_PATH}) idf_warn("IDF_PATH environment variable not found. " "Setting IDF_PATH to '${idf_path_infer}'.") set(idf_path "${idf_path_infer}") else() get_filename_component(idf_path_env "$ENV{IDF_PATH}" REALPATH) if(NOT "${idf_path_env}" STREQUAL "${idf_path_infer}") idf_warn("IDF_PATH environment variable is different from inferred IDF_PATH. " "Check if your project's top-level CMakeLists.txt includes the right " "CMake files. Environment IDF_PATH will be used for the build: " "'${idf_path_env}'") endif() set(idf_path "${idf_path_env}") endif() idf_build_set_property(IDF_PATH "${idf_path}") # Components reference either ${IDF_PATH} or ${idf_path}; publish both. set(IDF_PATH ${idf_path} PARENT_SCOPE) set(idf_path ${idf_path} PARENT_SCOPE) set(ENV{IDF_PATH} ${idf_path}) endfunction() #[[ __init_git() Determine the executable. Set the GIT build property. #]] function(__init_git) find_package(Git) if(NOT GIT_FOUND) idf_build_set_property(GIT NOTFOUND) idf_warn("Git executable not found.") return() endif() idf_build_set_property(GIT "${GIT_EXECUTABLE}") endfunction() #[[ __init_idf_version() Determine the IDF version from the version.txt file. If it is not present, use git-describe. If both previous attempts fail, use the IDF_VERSION from the environment variables as a fallback. Set IDF_VER build property. #]] function(__init_idf_version) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(git GIT) if(EXISTS "${idf_path}/version.txt") file(STRINGS "${idf_path}/version.txt" idf_ver) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${idf_path}/version.txt") else() # Try to get the version using git-describe. git_describe(idf_ver "${idf_path}" "--match=v*.*") if(NOT idf_ver) # The Git describe command failed unexpectedly, so the version is # set to IDF_VERSION as specified in version.cmake. set(idf_ver "$ENV{IDF_VERSION}") endif() endif() # Trim IDF_VER to the necessary 32 characters. string(SUBSTRING "${idf_ver}" 0 31 idf_ver) idf_build_set_property(IDF_VER ${idf_ver}) endfunction() #[[ __init_python() Determine Python interpreter, either from the PYTHON CMake cache variable or environmental variable or default it to "python". If the PYTHON_DEPS_CHECKED CMake cache variable is not set, check if all Python packages dependencies are satisfied. For instance, if a tool calling CMake has already performed this check, it doesn't need to be repeated. Set the global PYTHON variable, environment variable and build property. #]] function(__init_python) __get_default_value(VARIABLE PYTHON DEFAULT "python" OUTPUT python) file(TO_CMAKE_PATH ${python} python) idf_build_set_property(PYTHON "${python}") set(PYTHON "${python}" PARENT_SCOPE) if(PYTHON_DEPS_CHECKED) idf_dbg("Python dependencies have already been verified.") return() endif() idf_build_get_property(idf_path IDF_PATH) idf_msg("Checking Python dependencies...") execute_process( COMMAND "${python}" "${idf_path}/tools/idf_tools.py" "check-python-dependencies" RESULT_VARIABLE result ) if(result EQUAL 1) # The function check_python_dependencies returns an error code of 1 if # it fails. idf_die("Some Python dependencies must be installed. Check above message for details.") elseif(NOT result EQUAL 0) # This means that check_python_dependencies.py failed to run entirely, # and the result should be an error message. idf_die("Failed to run Python dependency check. Python: ${python}, Error: ${result}") endif() endfunction() #[[ __init_idf_target() Determine the IDF_TARGET value from the IDF_TARGET environment variable, the CMake cache variable, or the sdkconfig files. If none of these are set, use the default esp32 target. Ensure there are no inconsistencies in the IDF_TARGET values set in different locations. Set the IDF_TARGET as a global variable, in the CMake cache, as an environment variable, and as a build property. #]] function(__init_idf_target) set(sdkconfig_target "") set(target "") set(sdkconfig_file "") idf_build_get_property(sdkconfig SDKCONFIG) idf_build_get_property(sdkconfig_defaults SDKCONFIG_DEFAULTS) foreach(config ${sdkconfig} ${sdkconfig_defaults}) idf_dbg("Searching for target in '${config}'") __get_sdkconfig_option(OPTION CONFIG_IDF_TARGET SDKCONFIG "${config}" OUTPUT sdkconfig_target) if(sdkconfig_target) set(sdkconfig_file "${config}") break() endif() endforeach() __get_default_value(VARIABLE IDF_TARGET DEFAULT NOTFOUND OUTPUT target) if(NOT target) if(sdkconfig_target) idf_msg("IDF_TARGET is not set, guessed '${sdkconfig_target}' " "from sdkconfig '${sdkconfig_file}'") set(target "${sdkconfig_target}") else() idf_msg("IDF_TARGET not set, using default target: esp32") set(target "esp32") endif() endif() # Verify that the chosen target aligns with the CMake cache. set(cache_target $CACHE{IDF_TARGET}) if(cache_target) if(NOT "${cache_target}" STREQUAL "${target}") idf_die("IDF_TARGET '${cache_target}' in CMake cache does not match " "currently selected IDF_TARGET '${target}'. " "To change the target, clear the build directory and sdkconfig file, " "and build the project again.") endif() endif() # Verify that the chosen target aligns with the sdkconfig. if(sdkconfig_target AND "${sdkconfig_file}" STREQUAL "${sdkconfig}") if("$ENV{_IDF_PY_SET_TARGET_ACTION}" STREQUAL "1") idf_dbg("The target consistency check for the target specified in ${sdkconfig} " "was skipped because the set-target action is being executed.") elseif(NOT "${sdkconfig_target}" STREQUAL "${target}") idf_die("Target '${sdkconfig_target}' in sdkconfig '${sdkconfig}' " "does not match currently selected IDF_TARGET '${target}'. " "To change the target, clear the build directory and sdkconfig file, " "and build the project again.") endif() endif() idf_build_set_property(IDF_TARGET "${target}") set(ENV{IDF_TARGET} ${target}) set(IDF_TARGET ${target} CACHE STRING "IDF Build Target") endfunction() #[[ __init_toolchain() Determine the toolchain file, set IDF_TOOLCHAIN_FILE build property and global CMAKE_TOOLCHAIN_FILE CMake variable. Also ensure that the CMAKE_TOOLCHAIN_FILE is set to the correct file according to the current IDF_TARGET. Note: The IDF_TOOLCHAIN build property is set after the toolchain configuration in ``idf_project_init``. The ``tools/cmake/toolchain.cmake`` is included in the toolchain file and it sets the IDF_TOOLCHAIN variable in CMake's cache. #]] function(__init_toolchain) set(cache_toolchain $CACHE{IDF_TOOLCHAIN}) set(cache_toolchain_file $CACHE{CMAKE_TOOLCHAIN_FILE}) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(idf_target IDF_TARGET) __get_default_value(VARIABLE IDF_TOOLCHAIN DEFAULT gcc OUTPUT toolchain) if(cache_toolchain) if(NOT "${cache_toolchain}" STREQUAL "${toolchain}") idf_die("IDF_TOOLCHAIN '${cache_toolchain}' in CMake cache does not match " "currently selected IDF_TOOLCHAIN '${toolchain}'. To change " "the toolchain, clear the build directory and sdkconfig file, " "and build the project again.") endif() endif() if("${toolchain}" STREQUAL "clang") set(toolchain_type "clang-") endif() # Check that the selected target is consistent with the toolchain file in # the CMake cache. if(cache_toolchain_file) string(FIND "${cache_toolchain_file}" "-${toolchain_type}${idf_target}.cmake" found) if(${found} EQUAL -1) get_filename_component(cache_toolchain_file_stem "${cache_toolchain_file}" NAME_WE) idf_die("CMAKE_TOOLCHAIN_FILE '${cache_toolchain_file_stem}' " "does not match currently selected IDF_TARGET '${idf_target}'. " "To change the target, clear the build directory and sdkconfig file, " "and build the project again.") endif() endif() set(toolchain_file "${idf_path}/tools/cmake/toolchain-${toolchain_type}${idf_target}.cmake") if(NOT EXISTS ${toolchain_file}) idf_die("Toolchain file ${toolchain_file} not found") endif() # IDF_TOOLCHAIN applies to Espressif targets only; on linux (host build) # it must stay empty so Kconfig leaves both CONFIG_IDF_TOOLCHAIN_GCC and # CONFIG_IDF_TOOLCHAIN_CLANG unset. if(NOT "${idf_target}" STREQUAL "linux") set(IDF_TOOLCHAIN "${toolchain}" CACHE STRING "IDF Build Toolchain Type" FORCE) endif() set(CMAKE_TOOLCHAIN_FILE "${toolchain_file}" PARENT_SCOPE) idf_build_set_property(IDF_TOOLCHAIN_FILE "${toolchain_file}") endfunction() #[[ __init_ccache() Enable ccache if requested through CCACHE_ENABLE. #]] function(__init_ccache) if(NOT CCACHE_ENABLE) 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() endfunction() #[[ __init_components() Search for possible component directories categorized by their source, which could be ``idf_components``, ``project_extra_components``, or ``project_components``. Components added by the component manager are initialized later as ``project_managed_components`` after the component manager is called. The search respects the variables set by the user e.g. in the project's CMakeLists.txt file. These are maintained for backward compatibility. COMPONENT_DIRS If set, component directories are searched exclusively in the paths provided in ``COMPONENT_DIRS``. EXTRA_COMPONENT_DIRS Includes extra paths to search if ``COMPONENT_DIRS`` is not specified. EXTRA_COMPONENT_EXCLUDE_DIRS List of paths to exclude from searching the component directories. The ``PROJECT_COMPONENTS_SOURCE`` build property controls how the project's own components are categorised -- i.e. those discovered under ``CMAKE_CURRENT_SOURCE_DIR/main``, ``CMAKE_CURRENT_SOURCE_DIR/components``, or the paths listed in ``COMPONENT_DIRS``. It defaults to ``project_components`` (priority 3 -- highest). Setting it to ``idf_components`` (priority 0) makes them overridable by components supplied through ``EXTRA_COMPONENT_DIRS`` (priority 2). This is useful for sub-projects whose built-in components are provided by ESP-IDF and should be overridable by user-supplied components. Each component is initialized for every component directory found. #]] function(__init_components) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(prefix PREFIX) idf_build_get_property(project_components_source PROJECT_COMPONENTS_SOURCE) if(NOT project_components_source) set(project_components_source "project_components") endif() set(valid_sources "project_components" "idf_components") if(NOT project_components_source IN_LIST valid_sources) idf_die("Invalid PROJECT_COMPONENTS_SOURCE '${project_components_source}'." " Must be one of: ${valid_sources}") endif() __get_component_paths(PATHS "${idf_path}/components" OUTPUT idf_components) if(COMPONENT_DIRS) # The user explicitly stated the locations to search for components. # For backward compatibility, check that the paths in # COMPONENT_DIRS exist. __get_component_paths(PATHS ${COMPONENT_DIRS} EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS} SOURCE "COMPONENT_DIRS" CHECK OUTPUT project_components) else() __get_component_paths(PATHS "${CMAKE_CURRENT_SOURCE_DIR}/main" "${CMAKE_CURRENT_SOURCE_DIR}/components" EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS} OUTPUT project_components) if(EXTRA_COMPONENT_DIRS) # For backward compatibility, check that the paths in # EXTRA_COMPONENT_DIRS exist. __get_component_paths(PATHS ${EXTRA_COMPONENT_DIRS} EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS} SOURCE "EXTRA_COMPONENT_DIRS" CHECK OUTPUT project_extra_components) endif() endif() foreach(path IN LISTS idf_components) __init_component(DIRECTORY "${path}" PREFIX "${prefix}" SOURCE "idf_components") endforeach() foreach(path IN LISTS project_components) __init_component(DIRECTORY "${path}" PREFIX "${prefix}" SOURCE "${project_components_source}") endforeach() foreach(path IN LISTS project_extra_components) __init_component(DIRECTORY "${path}" PREFIX "${prefix}" SOURCE "project_extra_components") endforeach() endfunction() #[[ __init_submodules() Initialize submodules that are not yet initialized, and issue a warning for submodules that do not match the recorded hash in the git tree. #]] function(__init_submodules) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(git GIT) # For internal use: Skip the submodule check if running on GitLab CI # and the job is configured to not clone submodules. if("$ENV{IDF_SKIP_CHECK_SUBMODULES}" STREQUAL "1") idf_msg("skip submodule check on internal CI") return() endif() if(NOT git) idf_warn("Git executable was not found. Git submodule checks will not be executed. " "If you have any build issues at all, start by adding git executable to " "the PATH and rerun cmake to not see this warning again.") return() endif() execute_process( COMMAND ${git} submodule status WORKING_DIRECTORY ${idf_path} OUTPUT_VARIABLE status ERROR_VARIABLE stderr RESULT_VARIABLE rv ) if(rv) idf_warn("Git submodule status command failed(${rv}): ${stderr}" "Git submodule checks will not be performed. ") return() endif() __split(STRING "${status}" OUTPUT lines REMOVE_EMPTY) # The output of the git submodule status command is not guaranteed to be # stable. It may be necessary to check the GIT_VERSION_STRING and make # adjustments in the future. foreach(line IN LISTS lines) string(REGEX MATCH "(.)[0-9a-f]+ ([^\( ]+) ?" _ignored "${line}") set(status "${CMAKE_MATCH_1}") set(submodule_path "${CMAKE_MATCH_2}") if("${status}" STREQUAL "-") # missing submodule idf_msg("Initialising new submodule ${submodule_path}...") execute_process( COMMAND ${git} submodule update --init --recursive ${submodule_path} WORKING_DIRECTORY ${idf_path} ERROR_VARIABLE stderr RESULT_VARIABLE rv ) if(rv) idf_die("Git submodule '${submodule_path}' init failed(${rv}): ${stderr}") endif() elseif(NOT "${status}" STREQUAL " ") idf_warn("Git submodule ${submodule_path} is out of date. " "Run the following command to fix: " "git submodule update --init --recursive") endif() # Ensure CMake is rerun if the submodule's .git file is modified or # altered, such as in the case of an accidental deinitialization. get_filename_component(submodule_abs_path ${submodule_path} ABSOLUTE BASE_DIR ${idf_path}) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_abs_path}/.git) # If the HEAD file in the submodule's directory changes (i.e., if there # are commit changes), it will at least display the 'out of date' # warning. set(submodule_head "${idf_path}/.git/modules/${submodule_path}/HEAD") if(EXISTS "${submodule_head}") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_head}) endif() endforeach() endfunction() #[[ __init_idf_target_arch() Set the IDF_TARGET_ARCH value based on the sdkconfig. This means it must be initialized after the sdkconfig is generated and its CMake version is included. #]] function(__init_idf_target_arch) if(CONFIG_IDF_TARGET_ARCH_XTENSA) idf_build_set_property(IDF_TARGET_ARCH "xtensa") elseif(CONFIG_IDF_TARGET_ARCH_RISCV) idf_build_set_property(IDF_TARGET_ARCH "riscv") else() # Currently, no architecture is specified for Linux host builds. idf_build_set_property(IDF_TARGET_ARCH "") endif() endfunction() # The idf_build_properties interface target stores all global build # properties. It is created very early so that the initialization functions # can use it. Build properties are read and written with the # idf_build_get_property and idf_build_set_property functions. #[[api .. cmakev2:build_property:: IDF_PATH Absolute path to the ESP-IDF directory. .. cmakev2:build_property:: IDF_TARGET The target chip the project is built for, for example ``esp32``. .. cmakev2:build_property:: IDF_TARGET_ARCH The architecture of the target, ``xtensa`` or ``riscv`` (empty for the Linux host build). .. cmakev2:build_property:: IDF_VER The ESP-IDF version string. .. cmakev2:build_property:: IDF_TOOLCHAIN The selected toolchain, ``gcc`` or ``clang``. .. cmakev2:build_property:: PROJECT_NAME The project name. Defaults to the name passed to CMake's ``project()`` command. .. cmakev2:build_property:: PROJECT_VER The project version. .. cmakev2:build_property:: PROJECT_DIR Absolute path to the project directory. .. cmakev2:build_property:: BUILD_DIR Absolute path to the build directory. .. cmakev2:build_property:: PYTHON Path to the Python interpreter used by the build. .. cmakev2:build_property:: COMPONENTS_DISCOVERED List of the names of all discovered components. .. cmakev2:build_property:: COMPONENT_INTERFACES List of the interface targets of all discovered components. .. cmakev2:build_property:: COMPONENTS_INCLUDED List of the components that have been included (evaluated) in the build. .. cmakev2:build_property:: SDKCONFIG Path to the project ``sdkconfig`` file. .. cmakev2:build_property:: SDKCONFIG_DEFAULTS List of ``sdkconfig.defaults`` files applied to the configuration. .. cmakev2:build_property:: SDKCONFIG_HEADER Path to the generated ``sdkconfig.h`` with C and C++ preprocessor defines. .. cmakev2:build_property:: SDKCONFIG_CMAKE Path to the generated ``sdkconfig.cmake`` with the ``CONFIG_*`` CMake variables. .. cmakev2:build_property:: SDKCONFIG_JSON Path to the generated ``sdkconfig.json``. .. cmakev2:build_property:: COMPILE_OPTIONS Compile options applied to all components, for all languages. .. cmakev2:build_property:: C_COMPILE_OPTIONS Compile options applied to all components, for C only. .. cmakev2:build_property:: CXX_COMPILE_OPTIONS Compile options applied to all components, for C++ only. .. cmakev2:build_property:: ASM_COMPILE_OPTIONS Compile options applied to all components, for assembly only. .. cmakev2:build_property:: COMPILE_DEFINITIONS Preprocessor definitions applied to all components. .. cmakev2:build_property:: LINK_OPTIONS Link options applied when linking the application. .. cmakev2:build_property:: INCLUDE_DIRECTORIES Include directories applied to all components. .. cmakev2:build_property:: LINKER_TYPE The linker family, ``GNU`` or ``Darwin``. .. cmakev2:build_property:: IDF_COMPONENT_MANAGER Whether the component manager is enabled (``1``) or disabled (``0``). .. cmakev2:build_property:: IDF_COMPONENT_OPTIONAL_REQUIRES_MODE How :cmakev2:ref:`idf_component_optional_requires` is resolved, ``IMMEDIATE`` or ``DEFERRED``. #]] add_library(idf_build_properties INTERFACE) # The __idf_component_interface_cache target is used to maintain internal # mappings between component identifiers, such as component name or alias, and # the component interface target, which is the primary target for the # component. add_library(__idf_component_interface_cache INTERFACE) # Set build system prefix for component targets. idf_build_set_property(PREFIX "idf") # Set project directory property. idf_build_set_property(PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") # Set build directory property. idf_build_set_property(BUILD_DIR "${CMAKE_BINARY_DIR}") # Enable the generation of compile_commands.json. set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Initialize build system version. __init_build_version() # Initialize IDF_PATH and set it as a global and environmental variable, as # well as a build property. __init_idf_path() # Determine git executable and set GIT build property. __init_git() # Initialize git submodules. __init_submodules() # Initialize the IDF_VER build property. __init_idf_version() # Determine the Python interpreter and check package dependencies if necessary. __init_python() # Initialize Kconfig system infrastructure. __init_kconfig() # Initialize component manager build properties (IDF_COMPONENT_MANAGER, etc.). __init_component_manager() # Set IDF_TARGET. __init_idf_target() # Set IDF_TOOLCHAIN, IDF_TOOLCHAIN_FILE and CMAKE_TOOLCHAIN_FILE. __init_toolchain() # Enable ccache if requested. __init_ccache() #[[ At this point, the build system infrastructure is ready. Project-specific operations (component discovery, Kconfig generation, component manager, etc.) are handled in idf_project_init() after the project() call. #]]